Initial commit: 投注游戏平台初始化

This commit is contained in:
li
2026-02-25 01:26:58 +08:00
commit 77ca2cc8b3
275 changed files with 237479 additions and 0 deletions
BIN
View File
Binary file not shown.
+23
View File
@@ -0,0 +1,23 @@
<?php /** 底部5栏导航 */ $navActive = $navActive ?? ''; $t = $t ?? function($k){return $k;}; ?>
<nav class="nav-bottom">
<a href="/" class="nav-item <?=$navActive==='game'?'active':''?>">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="7" width="20" height="15" rx="2" ry="2"/><polyline points="17 2 12 7 7 2"/></svg>
<span><?=$t('nav_game')?></span>
</a>
<a href="/lottery" class="nav-item <?=$navActive==='lottery'?'active':''?>">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="20" x2="12" y2="10"/><line x1="18" y1="20" x2="18" y2="4"/><line x1="6" y1="20" x2="6" y2="16"/></svg>
<span><?=$t('nav_lottery')?></span>
</a>
<a href="/details" class="nav-item <?=$navActive==='details'?'active':''?>">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
<span><?=$t('nav_details')?></span>
</a>
<a href="#" class="nav-item <?=$navActive==='chat'?'active':''?>">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
<span><?=$t('nav_chat')?></span>
</a>
<a href="/profile" class="nav-item <?=$navActive==='profile'?'active':''?>">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
<span><?=$t('nav_profile')?></span>
</a>
</nav>
+263
View File
@@ -0,0 +1,263 @@
<?php use App\Core\I18n; I18n::init(); $t = function($k,$p=[]){return I18n::t($k,$p);}; $page=$page??'home'; ?>
<!DOCTYPE html><html lang="<?=I18n::getLang()?>">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>代理后台</title><script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<style>body{background:#0f172a;color:#fff;font-family:system-ui}.sidebar a.active{background:rgba(234,179,8,.15);color:#eab308;border-right:3px solid #eab308}</style>
</head>
<body class="min-h-screen flex">
<!-- 侧边栏 -->
<aside class="sidebar w-56 bg-slate-900 border-r border-white/5 min-h-screen hidden md:block shrink-0">
<div class="p-4 border-b border-white/5">
<div class="text-yellow-400 font-bold text-lg">🏎️ 代理后台</div>
<div class="text-white/40 text-xs mt-1"><?=htmlspecialchars($user['username']??'')?></div>
<div class="text-xs mt-1"><span class="text-white/30">邀请码:</span> <span class="text-yellow-400 font-mono"><?=htmlspecialchars($agent['agent_code']??'')?></span></div>
</div>
<nav class="p-2 space-y-1">
<a href="/agent" class="flex items-center gap-3 px-3 py-2 rounded text-sm hover:bg-white/5 <?=$page==='home'?'active':''?>"><i class="fas fa-home w-5 text-center"></i>数据总览</a>
<a href="/agent/odds" class="flex items-center gap-3 px-3 py-2 rounded text-sm hover:bg-white/5 <?=$page==='odds'?'active':''?>"><i class="fas fa-sliders-h w-5 text-center"></i>赔率设置</a>
<a href="/agent/bets" class="flex items-center gap-3 px-3 py-2 rounded text-sm hover:bg-white/5 <?=$page==='bets'?'active':''?>"><i class="fas fa-list-alt w-5 text-center"></i>投注记录</a>
<a href="/agent/commissions" class="flex items-center gap-3 px-3 py-2 rounded text-sm hover:bg-white/5 <?=$page==='commissions'?'active':''?>"><i class="fas fa-coins w-5 text-center"></i>佣金明细</a>
<div class="border-t border-white/5 my-2"></div>
<a href="/" class="flex items-center gap-3 px-3 py-2 rounded text-sm text-white/40 hover:bg-white/5"><i class="fas fa-gamepad w-5 text-center"></i>返回投注</a>
<a href="/logout" class="flex items-center gap-3 px-3 py-2 rounded text-sm text-red-400/60 hover:bg-white/5"><i class="fas fa-sign-out-alt w-5 text-center"></i>退出</a>
</nav>
</aside>
<!-- 手机顶部导航 -->
<div class="md:hidden fixed top-0 left-0 right-0 z-50 bg-slate-900 border-b border-white/5 px-4 py-2 flex items-center justify-between">
<span class="text-yellow-400 font-bold">🏎️ 代理后台</span>
<button onclick="document.getElementById('mobileMenu').classList.toggle('hidden')" class="text-white/60"><i class="fas fa-bars"></i></button>
</div>
<div id="mobileMenu" class="md:hidden fixed top-10 left-0 right-0 z-40 bg-slate-900 border-b border-white/5 hidden">
<div class="flex flex-wrap gap-1 p-2">
<a href="/agent" class="px-3 py-1 rounded text-xs <?=$page==='home'?'bg-yellow-500/20 text-yellow-400':'text-white/60'?>">总览</a>
<a href="/agent/odds" class="px-3 py-1 rounded text-xs <?=$page==='odds'?'bg-yellow-500/20 text-yellow-400':'text-white/60'?>">赔率</a>
<a href="/agent/bets" class="px-3 py-1 rounded text-xs <?=$page==='bets'?'bg-yellow-500/20 text-yellow-400':'text-white/60'?>">投注</a>
<a href="/agent/commissions" class="px-3 py-1 rounded text-xs <?=$page==='commissions'?'bg-yellow-500/20 text-yellow-400':'text-white/60'?>">佣金</a>
<a href="/" class="px-3 py-1 rounded text-xs text-white/40">返回</a>
</div>
</div>
<!-- 主内容 -->
<main class="flex-1 p-4 md:p-6 mt-12 md:mt-0 overflow-auto">
<?php if($page==='home'): ?>
<!-- ========== 数据总览 ========== -->
<h2 class="text-xl font-bold mb-4">📊 数据总览</h2>
<div class="grid grid-cols-2 md:grid-cols-4 gap-3 mb-6">
<div class="bg-white/5 rounded-xl p-4 text-center">
<div class="text-white/40 text-xs">玩家数</div>
<div class="text-2xl font-bold mt-1"><?=count($players??[])?></div>
</div>
<div class="bg-white/5 rounded-xl p-4 text-center">
<div class="text-white/40 text-xs">总投注额</div>
<div class="text-yellow-400 font-bold text-lg mt-1"><?=number_format($totalBets??0,2)?></div>
</div>
<div class="bg-white/5 rounded-xl p-4 text-center">
<div class="text-white/40 text-xs">今日佣金</div>
<div class="text-green-400 font-bold text-lg mt-1"><?=number_format($todayComm,2)?></div>
</div>
<div class="bg-white/5 rounded-xl p-4 text-center">
<div class="text-white/40 text-xs">累计佣金</div>
<div class="text-green-400 font-bold text-lg mt-1"><?=number_format($totalComm,2)?></div>
</div>
</div>
<!-- 分享链接 -->
<div class="bg-white/5 rounded-xl p-4 mb-4">
<h3 class="text-sm font-bold mb-2">📎 邀请链接</h3>
<div class="flex gap-2">
<input type="text" readonly value="<?=($_SERVER['REQUEST_SCHEME']??'http').'://'.$_SERVER['HTTP_HOST']?>/login?invite=<?=htmlspecialchars($agent['agent_code']??'')?>" class="flex-1 px-3 py-2 bg-white/5 border border-white/10 rounded text-xs text-white font-mono" id="shareLink">
<button onclick="navigator.clipboard.writeText(document.getElementById('shareLink').value);this.textContent='已复制!';setTimeout(()=>this.textContent='复制',1500)" class="px-4 py-2 bg-yellow-500 text-black rounded text-xs font-bold hover:bg-yellow-400">复制</button>
</div>
</div>
<!-- 下级代理 -->
<?php if(!empty($subAgents)): ?>
<div class="bg-white/5 rounded-xl p-4 mb-4">
<h3 class="text-sm font-bold mb-3">👥 下级代理</h3>
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead><tr class="text-white/40 text-xs border-b border-white/5"><th class="text-left py-2 px-2">用户名</th><th class="px-2">邀请码</th><th class="px-2">佣金%</th><th class="px-2">玩家数</th><th class="px-2">状态</th></tr></thead>
<tbody>
<?php foreach($subAgents as $sa): ?>
<tr class="border-b border-white/5 text-xs">
<td class="py-2 px-2"><?=htmlspecialchars($sa['user']['username']??'')?></td>
<td class="px-2 text-center font-mono text-yellow-400"><?=$sa['agent_code']?></td>
<td class="px-2 text-center"><?=$sa['commission_rate']?>%</td>
<td class="px-2 text-center"><?=$sa['player_count']?></td>
<td class="px-2 text-center"><?=$sa['status']?'<span class="text-green-400">启用</span>':'<span class="text-red-400">禁用</span>'?></td>
</tr>
<?php endforeach; ?>
</tbody></table></div>
</div>
<?php endif; ?>
<!-- 玩家列表 -->
<div class="bg-white/5 rounded-xl p-4">
<h3 class="text-sm font-bold mb-3">🎮 我的玩家</h3>
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead><tr class="text-white/40 text-xs border-b border-white/5"><th class="text-left py-2 px-2">ID</th><th class="text-left px-2">用户名</th><th class="px-2">余额</th><th class="px-2">注册时间</th><th class="px-2">状态</th></tr></thead>
<tbody>
<?php foreach($players??[] as $p): ?>
<tr class="border-b border-white/5 text-xs">
<td class="py-2 px-2 text-white/40"><?=$p['id']?></td>
<td class="px-2"><?=htmlspecialchars($p['username'])?></td>
<td class="px-2 text-center text-yellow-400"><?=number_format($p['balance'],2)?></td>
<td class="px-2 text-center text-white/40"><?=date('m-d H:i',strtotime($p['created_at']))?></td>
<td class="px-2 text-center"><?=$p['status']?'<span class="text-green-400">✓</span>':'<span class="text-red-400">✗</span>'?></td>
</tr>
<?php endforeach; ?>
<?php if(empty($players)): ?><tr><td colspan="5" class="text-center py-6 text-white/20">暂无玩家</td></tr><?php endif; ?>
</tbody></table></div>
</div>
<?php elseif($page==='odds'): ?>
<!-- ========== 赔率设置 ========== -->
<h2 class="text-xl font-bold mb-4">⚙️ 赔率设置</h2>
<p class="text-white/40 text-xs mb-4">设置你名下玩家的赔率。赔率不能超过上级设定的上限。</p>
<div class="space-y-4">
<?php
$oddsGroups = [
'大小 (Big/Small)' => ['type'=>'bs','targets'=>[]],
'单双 (Odd/Even)' => ['type'=>'oe','targets'=>[]],
'龙虎 (Dragon/Tiger)' => ['type'=>'dt','targets'=>[]],
'冠亚和大小 (Sum BS)' => ['type'=>'sum_bs','targets'=>[]],
];
// 大小
for($r=1;$r<=10;$r++){$oddsGroups['大小 (Big/Small)']['targets'][]=['target'=>"rank{$r}_big",'label'=>"第{$r}名 大"];$oddsGroups['大小 (Big/Small)']['targets'][]=['target'=>"rank{$r}_small",'label'=>"第{$r}名 小"];}
// 单双
for($r=1;$r<=10;$r++){$oddsGroups['单双 (Odd/Even)']['targets'][]=['target'=>"rank{$r}_odd",'label'=>"第{$r}名 单"];$oddsGroups['单双 (Odd/Even)']['targets'][]=['target'=>"rank{$r}_even",'label'=>"第{$r}名 双"];}
// 龙虎
$dtPairs=[[1,10],[2,9],[3,8],[4,7],[5,6]];
foreach($dtPairs as $i=>$p){$n=$i+1;$oddsGroups['龙虎 (Dragon/Tiger)']['targets'][]=['target'=>"dt{$n}_dragon",'label'=>"第{$p[0]}vs{$p[1]} 龙"];$oddsGroups['龙虎 (Dragon/Tiger)']['targets'][]=['target'=>"dt{$n}_tiger",'label'=>"第{$p[0]}vs{$p[1]} 虎"];}
// 冠亚和大小
$oddsGroups['冠亚和大小 (Sum BS)']['targets']=[['target'=>'sum_big','label'=>'和大'],['target'=>'sum_small','label'=>'和小'],['target'=>'sum_odd','label'=>'和单'],['target'=>'sum_even','label'=>'和双']];
?>
<?php foreach($oddsGroups as $groupName=>$group): ?>
<div class="bg-white/5 rounded-xl p-4">
<h3 class="text-sm font-bold mb-3"><?=$groupName?></h3>
<div class="grid grid-cols-2 md:grid-cols-4 gap-2">
<?php
// 同类型只显示一个代表(大小/单双每个名次赔率相同)
$type=$group['type'];
$firstTarget=$group['targets'][0]['target']??'';
$parentKey=$type.'_'.$firstTarget;
$parentVal=$parentMap[$parentKey]??1.95;
$myVal=$myMap[$parentKey]??$parentVal;
?>
<div class="col-span-full flex items-center gap-3 bg-white/5 rounded p-3">
<span class="text-white/40 text-xs w-24">统一赔率</span>
<span class="text-white/30 text-xs">上限: <?=$parentVal?></span>
<input type="number" step="0.01" min="1" max="<?=$parentVal?>" value="<?=$myVal?>" class="w-24 px-2 py-1 bg-white/10 border border-white/10 rounded text-sm text-white" data-type="<?=$type?>" data-targets='<?=json_encode(array_column($group['targets'],'target'))?>'>
</div>
</div>
</div>
<?php endforeach; ?>
<!-- 名次赔率 -->
<div class="bg-white/5 rounded-xl p-4">
<h3 class="text-sm font-bold mb-3">名次投注 (Rank) — 猜车号</h3>
<?php $parentRank=$parentMap['rank_rank1_1']??9.80; $myRank=$myMap['rank_rank1_1']??$parentRank; ?>
<div class="flex items-center gap-3 bg-white/5 rounded p-3">
<span class="text-white/40 text-xs w-24">统一赔率</span>
<span class="text-white/30 text-xs">上限: <?=$parentRank?></span>
<input type="number" step="0.01" min="1" max="<?=$parentRank?>" value="<?=$myRank?>" class="w-24 px-2 py-1 bg-white/10 border border-white/10 rounded text-sm text-white" id="rankOddsInput" data-type="rank">
</div>
</div>
<button onclick="saveAllOdds()" class="w-full py-3 bg-yellow-500 text-black font-bold rounded-xl hover:bg-yellow-400 text-sm">保存所有赔率</button>
</div>
<script>
async function saveAllOdds(){
const odds=[];
const gameId=<?=$game['id']??0?>;
// 收集各类型
document.querySelectorAll('input[data-type]').forEach(inp=>{
const type=inp.dataset.type;
const val=parseFloat(inp.value);
if(type==='rank'){
// 名次:100个target
for(let r=1;r<=10;r++)for(let c=1;c<=10;c++) odds.push({bet_type:'rank',bet_target:'rank'+r+'_'+c,odds:val});
} else if(inp.dataset.targets){
JSON.parse(inp.dataset.targets).forEach(t=>odds.push({bet_type:type,bet_target:t,odds:val}));
}
});
const r=await fetch('/api/agent/set-odds',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({game_id:gameId,odds:odds})});
const d=await r.json();
alert(d.message||'完成');if(d.success)location.reload();
}
</script>
<?php elseif($page==='bets'): ?>
<!-- ========== 投注记录 ========== -->
<h2 class="text-xl font-bold mb-4">📋 玩家投注记录</h2>
<div class="bg-white/5 rounded-xl overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead><tr class="text-white/40 text-xs border-b border-white/5 bg-white/5">
<th class="text-left py-3 px-3">玩家</th><th class="px-3">期号</th><th class="px-3">下注内容</th><th class="px-3">金额</th><th class="px-3">赔率</th><th class="px-3">状态</th><th class="px-3">赢额</th><th class="px-3">时间</th>
</tr></thead>
<tbody>
<?php foreach($betRecords??[] as $b):
// 投注类型中文转换
$bt=$b['bet_type']; $bv=$b['bet_value']; $label='';
if($bt==='rank'&&preg_match('/^rank(\d+)_(\d+)$/',$bv,$m)){$rn=(int)$m[1];$label=($rn===1?'冠军':($rn===2?'亚军':'第'.$rn.'名')).' '.$m[2].'号车';}
elseif($bt==='bs'&&preg_match('/^rank(\d+)_(big|small)$/',$bv,$m)){$rn=(int)$m[1];$label=($rn===1?'冠军':($rn===2?'亚军':'第'.$rn.'名')).' '.($m[2]==='big'?'大':'小');}
elseif($bt==='oe'&&preg_match('/^rank(\d+)_(odd|even)$/',$bv,$m)){$rn=(int)$m[1];$label=($rn===1?'冠军':($rn===2?'亚军':'第'.$rn.'名')).' '.($m[2]==='odd'?'单':'双');}
elseif($bt==='dt'&&preg_match('/^dt(\d+)_(dragon|tiger)$/',$bv,$m)){$ps=[1=>[1,10],2=>[2,9],3=>[3,8],4=>[4,7],5=>[5,6]];$p=$ps[(int)$m[1]]??[0,0];$label='龙虎 '.$p[0].'vs'.$p[1].' '.($m[2]==='dragon'?'龙':'虎');}
elseif($bt==='sum'&&preg_match('/^sum_(\d+)$/',$bv,$m)){$label='冠亚和 '.$m[1];}
elseif($bt==='sum_bs'){$sm=['sum_big'=>'和大','sum_small'=>'和小','sum_odd'=>'和单','sum_even'=>'和双'];$label=$sm[$bv]??$bv;}
else{$tm=['xiu'=>'小','tai'=>'大','chan'=>'双','le'=>'单','number'=>'点数','dice'=>'单骰','combo'=>'豹子','big_small'=>'大小','odd_even'=>'单双'];$vm=['big'=>'大','small'=>'小','odd'=>'单','even'=>'双','4red'=>'4红','4white'=>'4白','3red1white'=>'3红1白','1red3white'=>'1红3白'];$label=($tm[$bt]??$bt).' '.($vm[$bv]??$bv);}
?>
<tr class="border-b border-white/5 text-xs hover:bg-white/5">
<td class="py-2 px-3"><?=htmlspecialchars($b['username'])?></td>
<td class="px-3 font-mono text-white/40"><?=$b['period_number']?></td>
<td class="px-3"><?=htmlspecialchars($label)?></td>
<td class="px-3 text-yellow-400"><?=number_format($b['amount'],2)?></td>
<td class="px-3"><?=$b['odds']?></td>
<td class="px-3"><?php
$sc=['pending'=>'text-white/40','win'=>'text-green-400','lose'=>'text-red-400','settled'=>'text-blue-400'];
$sl=['pending'=>'待开','win'=>'赢','lose'=>'输','settled'=>'已结'];
echo '<span class="'.($sc[$b['status']]??'').'">'.($sl[$b['status']]??$b['status']).'</span>';
?></td>
<td class="px-3 <?=($b['win_amount']??0)>0?'text-green-400':'text-white/30'?>"><?=number_format($b['win_amount']??0,2)?></td>
<td class="px-3 text-white/30"><?=date('m-d H:i',strtotime($b['created_at']))?></td>
</tr>
<?php endforeach; ?>
<?php if(empty($betRecords)): ?><tr><td colspan="8" class="text-center py-8 text-white/20">暂无投注记录</td></tr><?php endif; ?>
</tbody></table></div>
</div>
<?php elseif($page==='commissions'): ?>
<!-- ========== 佣金明细 ========== -->
<h2 class="text-xl font-bold mb-4">💰 佣金明细</h2>
<div class="bg-white/5 rounded-xl overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead><tr class="text-white/40 text-xs border-b border-white/5 bg-white/5">
<th class="text-left py-3 px-3">来源玩家</th><th class="px-3">投注额</th><th class="px-3">佣金</th><th class="px-3">类型</th><th class="px-3">时间</th>
</tr></thead>
<tbody>
<?php foreach($records??[] as $r): ?>
<tr class="border-b border-white/5 text-xs hover:bg-white/5">
<td class="py-2 px-3"><?=htmlspecialchars($r['username'])?></td>
<td class="px-3 text-yellow-400"><?=number_format($r['bet_amount'],2)?></td>
<td class="px-3 text-green-400 font-bold"><?=number_format($r['commission'],2)?></td>
<td class="px-3"><?=$r['type']==='bet'?'投注佣金':'反水'?></td>
<td class="px-3 text-white/30"><?=date('m-d H:i',strtotime($r['created_at']))?></td>
</tr>
<?php endforeach; ?>
<?php if(empty($records)): ?><tr><td colspan="5" class="text-center py-8 text-white/20">暂无佣金记录</td></tr><?php endif; ?>
</tbody></table></div>
</div>
<?php endif; ?>
</main>
</body></html>
+356
View File
@@ -0,0 +1,356 @@
<?php
/**
* Xóc Đĩa 投注面板组件
* 从 xocdia.php 提取,可在 live.php 中复用
*
* 依赖变量:
* - $oddsList (array): 赔率列表
* - $game (array): 游戏信息
* - $currentPeriod (array): 当前期号
*/
// 赔率查找辅助函数
$getOdds = function($type, $target = 'all') use ($oddsList) {
if (!empty($oddsList)) {
foreach ($oddsList as $item) {
if ($item['type'] == $type && $item['target'] == $target) {
return (float)$item['odds'] == (int)$item['odds'] ? (int)$item['odds'] : $item['odds'];
}
}
}
// 默认值(防止赔率未配置)
if ($type == 'chan' || $type == 'le') return 0.96;
if ($type == 'exact') {
if (in_array($target, ['4red', '4white'])) return 10.0;
if (in_array($target, ['3red1white', '3white1red'])) return 3.5;
}
return 1;
};
?>
<style>
/* Xoc Dia Specific Styles */
.xocdia-top-row {
display: flex;
gap: 12px;
margin-bottom: 12px;
min-height: 100px;
}
.xocdia-big-btn {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 12px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: transform 0.2s, box-shadow 0.2s;
border: 2px solid rgba(255,255,255,0.1);
}
.xocdia-big-btn:hover {
transform: translateY(-2px);
filter: brightness(1.1);
}
.xocdia-big-btn.bet-chan {
background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 100%);
box-shadow: 0 4px 15px rgba(30, 58, 138, 0.4);
}
.xocdia-big-btn.bet-le {
background: linear-gradient(135deg, #b91c1c 0%, #991b1b 100%);
box-shadow: 0 4px 15px rgba(185, 28, 28, 0.4);
}
.xocdia-title {
font-size: 28px;
font-weight: 900;
color: #fff;
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
margin-bottom: 4px;
letter-spacing: 1px;
}
.xocdia-subtitle {
font-size: 11px;
color: rgba(255,255,255,0.8);
margin-bottom: 4px;
}
.xocdia-odds {
background: rgba(0,0,0,0.3);
padding: 2px 10px;
border-radius: 12px;
font-size: 12px;
font-weight: 700;
color: #ffd700;
border: 1px solid rgba(255,215,0,0.3);
}
.xocdia-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
margin-bottom: 15px;
}
.xocdia-cell {
background: linear-gradient(135deg, #ffffff 0%, #f1f5f9 100%);
border: 1px solid #cbd5e1;
border-radius: 12px;
padding: 8px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
cursor: pointer;
transition: all 0.2s;
min-height: 80px;
position: relative;
}
.xocdia-cell:hover {
transform: translateY(-2px);
border-color: #3b82f6;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.2);
}
.xocdia-dots {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
width: 28px;
}
.dot {
width: 12px;
height: 12px;
border-radius: 50%;
display: block;
box-shadow: inset 0 -1px 2px rgba(0,0,0,0.2);
}
.dot.red {
background-color: #ef4444;
border: 1px solid #dc2626;
}
.dot.white {
background-color: #fff;
border: 1px solid #cbd5e1;
}
.xocdia-label {
font-size: 11px;
font-weight: 800;
color: #334155;
text-align: center;
line-height: 1.2;
}
.xocdia-cell .bet-odds-mini {
margin-top: 0;
background: rgba(0,0,0,0.05);
color: #64748b;
}
/* Mobile Responsive Adjustments */
@media (max-width: 768px) {
.xocdia-grid {
grid-template-columns: repeat(2, 1fr);
}
.xocdia-top-row {
min-height: 80px;
}
}
/* PC Adjustments */
@media (min-width: 769px) {
.xocdia-top-row {
min-height: 120px;
}
.xocdia-grid {
grid-template-columns: repeat(4, 1fr);
}
}
</style>
<!-- Xóc Đĩa 投注面板 -->
<div class="live-panel bet-panel bet-panel-v2" style="position: relative;">
<!-- 状态遮罩层 -->
<div id="betOverlay" class="bet-overlay" style="position: absolute; inset: 0; background: rgba(0,0,0,0.6); z-index: 50; display: flex; align-items: center; justify-content: center; flex-direction: column; border-radius: 16px;">
<div id="countdownDisplay" class="countdown-number" style="display: none; font-size: 64px; color: #ffeb3b; font-weight: bold; text-shadow: 0 0 20px rgba(255,235,59,0.8);"></div>
<div id="statusDisplay" class="status-text" style="color: #fff; font-size: 24px; font-weight: bold; text-transform: uppercase; letter-spacing: 2px;">⏱️ ĐANG TẢI...</div>
</div>
<!-- 倒计时 -->
<div id="activeCountdown" style="text-align: right; margin-bottom: 10px; font-weight: bold; color: #333;">
<i class="fa-regular fa-clock"></i> <span id="activeTimer" style="color: #d93025; font-size: 18px;">60</span>s
</div>
<!-- 主要玩法:双/单 -->
<div class="xocdia-top-row">
<div class="xocdia-big-btn bet-chan" data-bet-type="chan" data-bet-value="all" data-odds="<?= $getOdds('chan', 'all') ?>">
<div class="xocdia-title">CHẴN</div>
<div class="xocdia-subtitle">4 Đỏ, 4 Trắng, 2 Đỏ 2 Trắng</div>
<div class="xocdia-odds">1:<?= $getOdds('chan', 'all') ?></div>
<div class="bet-amount-display" style="display:none"></div>
</div>
<div class="xocdia-big-btn bet-le" data-bet-type="le" data-bet-value="all" data-odds="<?= $getOdds('le', 'all') ?>">
<div class="xocdia-title">LẺ</div>
<div class="xocdia-subtitle">3 Đỏ 1 Trắng, 3 Trắng 1 Đỏ</div>
<div class="xocdia-odds">1:<?= $getOdds('le', 'all') ?></div>
<div class="bet-amount-display" style="display:none"></div>
</div>
</div>
<!-- 精确颜色组合 -->
<div class="xocdia-grid">
<div class="xocdia-cell" data-bet-type="exact" data-bet-value="4red" data-odds="<?= $getOdds('exact', '4red') ?>">
<div class="xocdia-dots">
<span class="dot red"></span><span class="dot red"></span>
<span class="dot red"></span><span class="dot red"></span>
</div>
<div class="xocdia-label">4 ĐỎ</div>
<div class="bet-odds-mini">1:<?= $getOdds('exact', '4red') ?></div>
<div class="bet-amount-display" style="display:none"></div>
</div>
<div class="xocdia-cell" data-bet-type="exact" data-bet-value="3red1white" data-odds="<?= $getOdds('exact', '3red1white') ?>">
<div class="xocdia-dots">
<span class="dot red"></span><span class="dot red"></span>
<span class="dot red"></span><span class="dot white"></span>
</div>
<div class="xocdia-label">3 ĐỎ 1 TRẮNG</div>
<div class="bet-odds-mini">1:<?= $getOdds('exact', '3red1white') ?></div>
<div class="bet-amount-display" style="display:none"></div>
</div>
<div class="xocdia-cell" data-bet-type="exact" data-bet-value="3white1red" data-odds="<?= $getOdds('exact', '3white1red') ?>">
<div class="xocdia-dots">
<span class="dot white"></span><span class="dot white"></span>
<span class="dot white"></span><span class="dot red"></span>
</div>
<div class="xocdia-label">3 TRẮNG 1 ĐỎ</div>
<div class="bet-odds-mini">1:<?= $getOdds('exact', '3white1red') ?></div>
<div class="bet-amount-display" style="display:none"></div>
</div>
<div class="xocdia-cell" data-bet-type="exact" data-bet-value="4white" data-odds="<?= $getOdds('exact', '4white') ?>">
<div class="xocdia-dots">
<span class="dot white"></span><span class="dot white"></span>
<span class="dot white"></span><span class="dot white"></span>
</div>
<div class="xocdia-label">4 TRẮNG</div>
<div class="bet-odds-mini">1:<?= $getOdds('exact', '4white') ?></div>
<div class="bet-amount-display" style="display:none"></div>
</div>
</div>
<!-- 操作按钮 -->
<div class="bet-actions bet-actions-v2">
<button class="bet-action bet-action-green" type="button" id="confirmBetBtn">
<i class="fa-solid fa-check"></i> <span>ĐẶT CƯỢC</span>
</button>
<button class="bet-action bet-action-red" type="button" id="cancelBetBtn">
<i class="fa-solid fa-xmark"></i> <span>HỦY</span>
</button>
</div>
<!-- 筹码选择 -->
<div class="bet-chips bet-chips-v2">
<button class="bet-chip-btn" data-amount="100000" data-label="100K">
<div class="bet-chip-display chip-red">100K</div>
</button>
<button class="bet-chip-btn" data-amount="500000" data-label="500K">
<div class="bet-chip-display chip-blue">500K</div>
</button>
<button class="bet-chip-btn" data-amount="1000000" data-label="1M">
<div class="bet-chip-display chip-gold">1M</div>
</button>
<button class="bet-chip-btn" data-amount="5000000" data-label="5M">
<div class="bet-chip-display chip-green">5M</div>
</button>
</div>
</div>
<script>
// Xóc Đĩa 投注逻辑(使用全局 betState)
// 确保这段代码只在 Xóc Đĩa 游戏时执行
if (typeof window.xocdiaInitialized === 'undefined') {
window.xocdiaInitialized = true;
// 等待 live.php 的主脚本加载完成
document.addEventListener('DOMContentLoaded', function() {
layui.use(['layer'], function(){
var layer = layui.layer;
// 使用全局 betState(由 live.php 创建)
// 不要创建新的 betState,避免冲突
// 筹码选择已经由 live.php 处理,不需要重复绑定
// --- Bet Placement (Xóc Đĩa specific) ---
document.querySelectorAll('.xocdia-big-btn, .xocdia-cell').forEach(el => {
el.addEventListener('click', function() {
// 使用全局 betState
if(!window.betState || !window.betState.selectedChip) {
layer.msg('Vui lòng chọn chip (số tiền) trước!');
return;
}
var type = this.getAttribute('data-bet-type');
var value = this.getAttribute('data-bet-value');
var odds = parseFloat(this.getAttribute('data-odds'));
var key = type + '_' + value;
// Add to global bet state (使用与 live.php 一致的数据结构)
if(!window.betState.bets[key]) {
window.betState.bets[key] = {
amount: 0,
chipInfo: window.betState.selectedChip,
betInfo: {
type: type,
value: value,
odds: odds
}
};
}
window.betState.bets[key].amount += window.betState.selectedChip.amount;
// Visual Update
updateBetVisual(this, window.betState.bets[key].amount, window.betState.selectedChip.color);
this.classList.add('bet-selected');
});
});
function updateBetVisual(el, totalAmount, color) {
var display = el.querySelector('.bet-amount-display');
display.style.display = 'flex';
display.innerHTML = `
<div class="bet-chip-wrapper">
<div class="bet-chip-icon chip-${color}"></div>
</div>
<div class="bet-amount-text">${formatAmount(totalAmount)}</div>
`;
}
function formatAmount(n) {
if(n >= 1000000) return (n/1000000) + 'M';
if(n >= 1000) return (n/1000) + 'K';
return n;
}
// --- Actions ---
// 取消按钮已经由 live.php 处理
// 确认按钮已经由 live.php 处理
// 不需要重复绑定确认和取消按钮
// live.php 的主脚本会处理这些按钮
});
});
}
</script>
+82
View File
@@ -0,0 +1,82 @@
<?php use App\Core\I18n; I18n::init(); $t = function($k,$p=[]){return I18n::t($k,$p);};
// 投注翻译函数
function fmtBet($type,$val,$t){
if($type==='rank'&&preg_match('/^rank(\d+)_(\d+)$/',$val,$m)){$r=(int)$m[1];$rl=$r===1?$t('champion'):($r===2?$t('runner_up'):$t('rank_n',['n'=>$r]));return $rl.' #'.$m[2];}
if($type==='bs'&&preg_match('/^rank(\d+)_(big|small)$/',$val,$m)){$r=(int)$m[1];$rl=$r===1?$t('champion'):($r===2?$t('runner_up'):$t('rank_n',['n'=>$r]));return $rl.' '.($m[2]==='big'?$t('big'):$t('small'));}
if($type==='oe'&&preg_match('/^rank(\d+)_(odd|even)$/',$val,$m)){$r=(int)$m[1];$rl=$r===1?$t('champion'):($r===2?$t('runner_up'):$t('rank_n',['n'=>$r]));return $rl.' '.($m[2]==='odd'?$t('odd'):$t('even'));}
if($type==='dt'&&preg_match('/^dt(\d+)_(dragon|tiger)$/',$val,$m)){$ps=[1=>[1,10],2=>[2,9],3=>[3,8],4=>[4,7],5=>[5,6]];$p=$ps[(int)$m[1]]??[0,0];return $t('rank_n',['n'=>$p[0]]).'vs'.$t('rank_n',['n'=>$p[1]]).' '.($m[2]==='dragon'?$t('dragon'):$t('tiger'));}
if($type==='sum'&&preg_match('/^sum_(\d+)$/',$val,$m))return $t('sum').' '.$m[1];
if($type==='sum_bs'){$map=['sum_big'=>$t('sum_big'),'sum_small'=>$t('sum_small'),'sum_odd'=>$t('sum_odd'),'sum_even'=>$t('sum_even')];return $map[$val]??$val;}
return $type.':'.$val;
}
?>
<!DOCTYPE html><html lang="<?=I18n::getLang()?>">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title><?=$t('bet_details')?></title>
<link rel="stylesheet" href="/Static/css/app.css">
<style>
.det-header{background:#fff;padding:16px 15px;box-shadow:0 2px 8px rgba(0,0,0,.04);position:sticky;top:0;z-index:100}
.det-tabs{display:flex;background:#fff;border-bottom:1px solid #F0F0F0;margin-top:0}
.det-tab{flex:1;text-align:center;padding:15px 0;font-size:15px;color:#666;cursor:pointer;position:relative;transition:all .3s;background:none;border:none}
.det-tab.active{color:#333;font-weight:600}
.det-tab.active::after{content:'';position:absolute;bottom:0;left:50%;transform:translateX(-50%);width:60px;height:3px;background:linear-gradient(90deg,#1E90FF,#4A9FFF);border-radius:2px}
.table-header{background:#fff;display:flex;padding:12px 15px;border-bottom:1px solid #F0F0F0;font-size:13px;color:#999}
.bet-row{background:#fff;display:flex;align-items:center;padding:14px 15px;border-bottom:1px solid #F5F5F5;transition:background .2s}
.bet-row:active{background:#FAFAFA}
.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:80px 20px;background:#fff;min-height:50vh}
.empty-icon{width:80px;height:80px;border-radius:12px;background:#E8E8E8;display:flex;align-items:center;justify-content:center;margin-bottom:20px}
.empty-text{font-size:15px;color:#999}
.col-info{flex:1;min-width:0}.col-amount{flex:0 0 80px;text-align:right}.col-result{flex:0 0 70px;text-align:right}
.status-win{color:var(--success);font-weight:600}.status-lose{color:var(--danger);font-weight:600}.status-pending{color:var(--warn);font-weight:600}
</style>
</head>
<body style="background:#F5F5F5;padding-bottom:60px">
<div class="det-header">
<div style="font-weight:700;font-size:18px;color:#333"><?=$t('bet_details')?></div>
</div>
<div class="det-tabs">
<button class="det-tab active" onclick="showPanel('settled',this)"><?=$t('settled_details')?></button>
<button class="det-tab" onclick="showPanel('pending',this)"><?=$t('unsettled_details')?></button>
</div>
<div id="panelSettled">
<?php if(empty($settled)): ?>
<div class="empty-state"><div class="empty-icon"><svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="#999" stroke-width="1.5"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg></div><div class="empty-text"><?=$t('no_data')?></div></div>
<?php else: ?>
<div class="table-header"><span class="col-info"><?=$t('play_type')?></span><span class="col-amount"><?=$t('amount')?></span><span class="col-result"><?=$t('result')?></span></div>
<?php foreach($settled as $b): ?>
<div class="bet-row">
<div class="col-info">
<div style="font-size:14px;color:#333;font-weight:500"><?=htmlspecialchars(fmtBet($b['bet_type'],$b['bet_value'],$t))?></div>
<div style="font-size:11px;color:#999;margin-top:2px"><?=$b['period_number']??''?> · <?=$b['created_at']??''?></div>
</div>
<div class="col-amount" style="font-size:14px;color:#333"><?=number_format((float)$b['amount'],2)?><br><span style="font-size:10px;color:#999">x<?=$b['odds']??''?></span></div>
<div class="col-result"><span class="status-<?=$b['status']?>"><?=$b['status']==='win'?'+'.number_format((float)$b['win_amount'],2):($b['status']==='lose'?'-'.number_format((float)$b['amount'],2):$b['status'])?></span></div>
</div>
<?php endforeach; endif; ?>
</div>
<div id="panelPending" class="hidden">
<?php if(empty($pending)): ?>
<div class="empty-state"><div class="empty-icon"><svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="#999" stroke-width="1.5"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg></div><div class="empty-text"><?=$t('no_data')?></div></div>
<?php else: ?>
<div class="table-header"><span class="col-info"><?=$t('play_type')?></span><span class="col-amount"><?=$t('amount')?></span><span class="col-result"><?=$t('status')?></span></div>
<?php foreach($pending as $b): ?>
<div class="bet-row">
<div class="col-info">
<div style="font-size:14px;color:#333;font-weight:500"><?=htmlspecialchars(fmtBet($b['bet_type'],$b['bet_value'],$t))?></div>
<div style="font-size:11px;color:#999;margin-top:2px"><?=$b['period_number']??''?> · <?=$b['created_at']??''?></div>
</div>
<div class="col-amount" style="font-size:14px;color:#333"><?=number_format((float)$b['amount'],2)?><br><span style="font-size:10px;color:#999">x<?=$b['odds']??''?></span></div>
<div class="col-result"><span class="status-pending"><?=$t('awaiting_draw')?></span></div>
</div>
<?php endforeach; endif; ?>
</div>
<?php $navActive='details'; include __DIR__.'/_nav.php'; ?>
<script>
function showPanel(id,btn){document.getElementById('panelSettled').classList.toggle('hidden',id!=='settled');document.getElementById('panelPending').classList.toggle('hidden',id!=='pending');document.querySelectorAll('.det-tab').forEach(t=>t.classList.remove('active'));btn.classList.add('active');}
</script>
</body></html>
+225
View File
@@ -0,0 +1,225 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>操作提示</title>
<!-- 引入Font Awesome图标库 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
min-height: 100vh;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.error-container {
background-color: #ffffff;
width: 100%;
max-width: 600px;
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
overflow: hidden;
transition: transform 0.3s ease;
}
.error-container:hover {
transform: translateY(-5px);
}
.error-header {
background: linear-gradient(135deg, #ff6b6b 0%, #ee5253 100%);
color: white;
padding: 30px 20px;
text-align: center;
position: relative;
overflow: hidden;
}
.error-header::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.1'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
opacity: 0.4;
}
.error-icon {
font-size: 50px;
margin-bottom: 15px;
position: relative;
z-index: 1;
animation: pulse 2s infinite;
}
.error-title {
font-size: 24px;
font-weight: 600;
margin-bottom: 8px;
position: relative;
z-index: 1;
}
.error-subtitle {
font-size: 16px;
opacity: 0.9;
position: relative;
z-index: 1;
}
.error-body {
padding: 30px 20px;
text-align: center;
}
.error-message {
font-size: 18px;
color: #4a4a4a;
line-height: 1.6;
margin-bottom: 30px;
padding: 0 20px;
min-height: 60px;
display: flex;
align-items: center;
justify-content: center;
}
.error-actions {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 15px;
}
.btn {
padding: 12px 24px;
border-radius: 30px;
border: none;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-refresh {
background-color: #4285f4;
color: white;
}
.btn-refresh:hover {
background-color: #3367d6;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(66, 133, 244, 0.3);
}
.btn-back {
background-color: #f1f3f4;
color: #202124;
}
.btn-back:hover {
background-color: #e8eaed;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.btn-home {
background-color: #34a853;
color: white;
}
.btn-home:hover {
background-color: #2d8643;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(52, 168, 83, 0.3);
}
.footer-note {
margin-top: 30px;
color: #868686;
font-size: 14px;
opacity: 0.8;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
@media (max-width: 480px) {
.error-header {
padding: 20px 15px;
}
.error-icon {
font-size: 40px;
}
.error-title {
font-size: 20px;
}
.error-body {
padding: 20px 15px;
}
.error-message {
font-size: 16px;
margin-bottom: 20px;
}
.btn {
width: 100%;
justify-content: center;
padding: 10px 20px;
font-size: 15px;
}
}
</style>
</head>
<body>
<div class="error-container">
<div class="error-header">
<div class="error-icon">
<i class="fas fa-exclamation-circle"></i>
</div>
<h2 class="error-title">操作提示</h2>
<p class="error-subtitle">很抱歉,出现了一些问题</p>
</div>
<div class="error-body">
<div class="error-message">
<?php echo htmlspecialchars($error); // 显示错误信息 ?>
</div>
<div class="error-actions">
<button class="btn btn-refresh" onclick="window.location.reload()">
<i class="fas fa-sync-alt"></i> 刷新页面
</button>
<button class="btn btn-back" onclick="window.history.back()">
<i class="fas fa-arrow-left"></i> 返回上一页
</button>
<button class="btn btn-home" onclick="window.location.href='/'">
<i class="fas fa-home"></i> 首页
</button>
</div>
<p class="footer-note">如有疑问,请联系系统管理员</p>
</div>
</div>
</body>
</html>
+97
View File
@@ -0,0 +1,97 @@
<?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>PK10 - <?=$t('home')?></title>
<link rel="stylesheet" href="/Static/css/app.css">
<style>
.user-card{margin:15px;padding:16px;background:#fff;border-radius:16px;display:flex;align-items:center;gap:12px;box-shadow:0 2px 8px rgba(0,0,0,.08)}
.user-avatar{width:44px;height:44px;border-radius:50%;background:#E8F4FF;display:flex;align-items:center;justify-content:center}
.user-avatar svg{width:24px;height:24px;color:#1E90FF}
.user-info{flex:1}
.user-name{font-size:16px;font-weight:600;color:#333;margin-bottom:4px}
.user-balance{font-size:20px;font-weight:700;color:#333}
.action-buttons{display:flex;gap:8px}
.act-btn{display:flex;flex-direction:column;align-items:center;gap:4px;cursor:pointer;background:none;border:none;padding:0}
.act-btn svg{width:24px;height:24px}
.act-btn span{font-size:12px;color:#666}
.nav-tabs{display:flex;justify-content:space-around;align-items:center;height:50px;background:#fff;border-bottom:1px solid #F0F0F0}
.nav-tab{font-size:15px;color:#666;cursor:pointer;position:relative;padding:12px 0;background:none;border:none}
.nav-tab.active{color:#1E90FF;font-weight:600}
.nav-tab.active::after{content:'';position:absolute;bottom:0;left:0;right:0;height:2px;background:#1E90FF}
.content-area{display:flex;min-height:calc(100vh - 200px)}
.left-menu{width:70px;background:#fff;padding:8px 0;border-right:1px solid #F0F0F0}
.left-menu .mi{height:50px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;font-size:12px;color:#666;cursor:pointer}
.left-menu .mi.active{color:#1E90FF;background:#F0F8FF}
.left-menu .mi svg{width:20px;height:20px}
.game-area{flex:1;padding:12px;background:#F5F5F5}
.game-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.game-card{height:100px;border-radius:12px;position:relative;display:block;cursor:pointer;overflow:hidden;background:#fff;box-shadow:0 2px 8px rgba(0,0,0,.06);text-decoration:none}
.game-card:active{transform:scale(.98)}
.game-card-inner{width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;background:linear-gradient(135deg,#F0F8FF,#E0EFFF);border-radius:12px}
.game-card-inner svg{width:36px;height:36px;color:#1E90FF}
.game-card-inner .gname{font-size:13px;font-weight:600;color:#333}
.game-card.disabled{opacity:.5;pointer-events:none}
.section-title{font-size:16px;font-weight:600;color:#333;margin-bottom:12px;padding:0 0 0 4px}
</style>
</head>
<body style="background:#F5F5F5;padding-bottom:60px">
<!-- 用户卡片 -->
<div class="user-card">
<div class="user-avatar"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg></div>
<div class="user-info">
<div class="user-name"><?=htmlspecialchars($user['username']??'')?></div>
<div class="user-balance"><?=number_format($user['balance']??0,2)?></div>
</div>
<div class="action-buttons">
<button class="act-btn" onclick="alert('<?=$t('deposit')?>')">
<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')?>')">
<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')?>')">
<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>
</div>
</div>
<!-- 导航标签 -->
<div class="nav-tabs">
<button class="nav-tab active"><?=$t('lottery_tab')?></button>
</div>
<!-- 内容区 -->
<div class="content-area">
<div class="left-menu">
<div class="mi active">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polygon points="10 8 16 12 10 16 10 8"/></svg>
<span><?=$t('racing')?></span>
</div>
</div>
<div class="game-area">
<div class="section-title"><?=$t('racing')?></div>
<div class="game-grid">
<?php if($game): ?>
<a href="/game/pk10" class="game-card">
<div class="game-card-inner">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="7" cy="17" r="2"/><circle cx="17" cy="17" r="2"/><path d="M5 17H3v-4l2-5h9l4 5h3v4h-2"/><path d="M5 12h14"/></svg>
<div class="gname"><?=htmlspecialchars($game['name']??$t('pk10_title'))?></div>
</div>
</a>
<?php endif; ?>
<div class="game-card disabled">
<div class="game-card-inner" style="background:linear-gradient(135deg,#F5F5F5,#EBEBEB)">
<svg viewBox="0 0 24 24" fill="none" stroke="#999" stroke-width="2"><rect x="2" y="7" width="20" height="15" rx="2" ry="2"/><polyline points="17 2 12 7 7 2"/></svg>
<div class="gname" style="color:#999"><?=$t('more_games')?></div>
</div>
</div>
</div>
</div>
</div>
<?php $navActive='game'; include __DIR__.'/_nav.php'; ?>
</body></html>
+15
View File
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<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>
<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">
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-900 text-white min-h-screen">
<?= $Content ?? '' ?>
</body>
</html>
+1826
View File
File diff suppressed because it is too large Load Diff
+80
View File
@@ -0,0 +1,80 @@
<?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>PK10 - <?=$t('login_title')?></title>
<link rel="stylesheet" href="/Static/css/app.css">
<style>
body{background:linear-gradient(135deg,#E8F4F8,#F0F0F0);min-height:100vh;display:flex;align-items:center;justify-content:center;padding:16px}
.auth-card{background:#fff;border-radius:24px;padding:32px 24px;box-shadow:0 8px 32px rgba(0,0,0,.1);width:100%;max-width:400px}
.auth-tabs{display:flex;background:#F5F5F5;border-radius:12px;padding:4px;margin-bottom:24px}
.auth-tab{flex:1;padding:10px;text-align:center;border-radius:10px;font-size:14px;font-weight:600;color:var(--text3);cursor:pointer;transition:all .2s;border:none;background:none}
.auth-tab.active{background:var(--primary);color:#fff;box-shadow:0 2px 8px rgba(30,144,255,.3)}
.auth-input{width:100%;padding:14px 16px;background:#F8F9FA;border:1px solid var(--border);border-radius:12px;font-size:14px;color:var(--text);outline:none;transition:border .2s}
.auth-input:focus{border-color:var(--primary);background:#fff;box-shadow:0 0 0 3px rgba(30,144,255,.1)}
.auth-input::placeholder{color:#bbb}
.auth-btn{width:100%;padding:14px;border:none;border-radius:12px;font-size:15px;font-weight:700;cursor:pointer;transition:all .2s}
.auth-btn-login{background:linear-gradient(135deg,var(--primary),var(--primary-dark));color:#fff}
.auth-btn-login:hover{transform:translateY(-1px);box-shadow:0 4px 16px rgba(30,144,255,.35)}
.auth-btn-reg{background:linear-gradient(135deg,var(--success),#388E3C);color:#fff}
.auth-btn-reg:hover{transform:translateY(-1px);box-shadow:0 4px 16px rgba(76,175,80,.35)}
.lang-bar{display:flex;justify-content:center;gap:6px;margin-bottom:16px}
.lang-btn{padding:4px 10px;border-radius:16px;font-size:11px;text-decoration:none;transition:all .2s}
.lang-btn.active{background:var(--primary);color:#fff}
.lang-btn:not(.active){background:#fff;color:var(--text3);border:1px solid var(--border)}
.code-row{display:flex;gap:8px}
.code-btn{padding:14px 16px;background:rgba(30,144,255,.1);color:var(--primary);border:none;border-radius:12px;font-size:13px;white-space:nowrap;cursor:pointer;font-weight:600}
.remember-row{display:flex;align-items:center;gap:6px;font-size:13px;color:var(--text2)}
#msg{margin-top:12px;text-align:center;font-size:13px}
.msg-ok{color:var(--success)}.msg-err{color:var(--danger)}
</style>
</head>
<body>
<div style="width:100%;max-width:400px">
<div class="lang-bar">
<?php foreach(I18n::LANGUAGES as $code=>$name): ?>
<a href="?lang=<?=$code?>" class="lang-btn <?=I18n::getLang()===$code?'active':''?>"><?=$name?></a>
<?php endforeach; ?>
</div>
<div class="auth-card">
<div style="text-align:center;margin-bottom:20px">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#1E90FF" stroke-width="2"><circle cx="7" cy="17" r="2"/><circle cx="17" cy="17" r="2"/><path d="M5 17H3v-4l2-5h9l4 5h3v4h-2"/><path d="M5 12h14"/></svg>
<h1 style="font-size:22px;font-weight:800;color:#1E90FF;margin:4px 0">PK10</h1>
<p style="font-size:12px;color:var(--text3)"><?=$t('app_name')?></p>
</div>
<div class="auth-tabs">
<button onclick="showTab('login')" id="tabLogin" class="auth-tab active"><?=$t('login')?></button>
<button onclick="showTab('register')" id="tabRegister" class="auth-tab"><?=$t('register')?></button>
</div>
<form id="loginForm" onsubmit="return doLogin(event)">
<div class="space-y-4">
<input type="text" id="loginUser" placeholder="<?=$t('username')?>" required class="auth-input">
<input type="password" id="loginPass" placeholder="<?=$t('password')?>" required class="auth-input">
<label class="remember-row"><input type="checkbox" id="loginRemember"> <?=$t('remember_me')?></label>
<button type="submit" class="auth-btn auth-btn-login"><?=$t('login')?></button>
</div>
</form>
<form id="registerForm" class="hidden" onsubmit="return doRegister(event)">
<div class="space-y-4">
<input type="text" id="regUser" placeholder="<?=$t('username')?>" required class="auth-input">
<input type="email" id="regEmail" placeholder="<?=$t('email')?>" required class="auth-input">
<div class="code-row">
<input type="text" id="regCode" placeholder="<?=$t('verify_code')?>" required class="auth-input" style="flex:1">
<button type="button" onclick="sendCode()" id="sendCodeBtn" class="code-btn"><?=$t('send_code')?></button>
</div>
<input type="password" id="regPass" placeholder="<?=$t('password')?>" required class="auth-input">
<input type="password" id="regPass2" placeholder="<?=$t('confirm_password')?>" required class="auth-input">
<input type="text" id="regInvite" placeholder="<?=$t('invite_code_optional')?>" class="auth-input">
<button type="submit" class="auth-btn auth-btn-reg"><?=$t('register')?></button>
</div>
</form>
</div>
<div id="msg" class="hidden"></div>
</div>
<script>
function showTab(t){document.getElementById('loginForm').classList.toggle('hidden',t!=='login');document.getElementById('registerForm').classList.toggle('hidden',t!=='register');document.getElementById('tabLogin').className='auth-tab '+(t==='login'?'active':'');document.getElementById('tabRegister').className='auth-tab '+(t==='register'?'active':'');}
function showMsg(m,ok){const e=document.getElementById('msg');e.textContent=m;e.className='mt-2 text-center text-sm '+(ok?'msg-ok':'msg-err');}
async function doLogin(e){e.preventDefault();const r=await fetch('/api/auth/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:document.getElementById('loginUser').value,password:document.getElementById('loginPass').value,remember:document.getElementById('loginRemember').checked})});const d=await r.json();if(d.success)window.location.href=d.redirect||'/';else showMsg(d.message,false);return false;}
async function doRegister(e){e.preventDefault();const r=await fetch('/api/auth/register',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:document.getElementById('regUser').value,email:document.getElementById('regEmail').value,verify_code:document.getElementById('regCode').value,password:document.getElementById('regPass').value,confirm_password:document.getElementById('regPass2').value,invite_code:document.getElementById('regInvite').value})});const d=await r.json();showMsg(d.message,d.success);if(d.success)setTimeout(()=>showTab('login'),1500);return false;}
let cdTimer=0;async function sendCode(){if(cdTimer>0)return;const email=document.getElementById('regEmail').value;if(!email){showMsg('Enter email',false);return;}const r=await fetch('/api/auth/send-code',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({email})});const d=await r.json();showMsg(d.message,d.success);if(d.success){cdTimer=60;const btn=document.getElementById('sendCodeBtn');const tick=setInterval(()=>{cdTimer--;btn.textContent=cdTimer+'s';if(cdTimer<=0){clearInterval(tick);btn.textContent='<?=$t('send_code')?>';cdTimer=0;}},1000);}}
</script>
</body></html>
+43
View File
@@ -0,0 +1,43 @@
<?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('lottery_history')?></title>
<link rel="stylesheet" href="/Static/css/app.css">
<style>
.lotto-header{background:#fff;padding:12px 16px;border-bottom:1px solid var(--border);position:sticky;top:0;z-index:10;display:flex;align-items:center;justify-content:space-between}
.period-row{background:#fff;border-radius:12px;padding:12px;margin-bottom:8px;box-shadow:0 1px 4px rgba(0,0,0,.04)}
.balls{display:flex;gap:3px;flex-wrap:wrap;margin-top:6px}
.ball{width:44px;height:44px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px;color:#fff}
</style>
</head>
<body class="pb-nav" style="background:var(--bg)">
<div class="lotto-header">
<span style="font-weight:700;font-size:16px;display:flex;align-items:center;gap:6px"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#1E90FF" stroke-width="2"><line x1="12" y1="2" x2="12" y2="6"/><line x1="12" y1="18" x2="12" y2="22"/><line x1="4.93" y1="4.93" x2="7.76" y2="7.76"/><line x1="16.24" y1="16.24" x2="19.07" y2="19.07"/><line x1="2" y1="12" x2="6" y2="12"/><line x1="18" y1="12" x2="22" y2="12"/><line x1="4.93" y1="19.07" x2="7.76" y2="16.24"/><line x1="16.24" y1="7.76" x2="19.07" y2="4.93"/></svg><?=$t('lottery_history')?></span>
<span style="font-size:12px;color:var(--text3)"><?=htmlspecialchars($game['name']??'PK10')?></span>
</div>
<div style="padding:12px;max-width:600px;margin:0 auto">
<?php if(empty($periods)): ?>
<div style="text-align:center;color:var(--text3);padding:40px;font-size:13px"><?=$t('no_data')?></div>
<?php else: foreach($periods as $p): ?>
<div class="period-row">
<div style="display:flex;justify-content:space-between;align-items:center">
<span style="font-size:13px;font-weight:600;color:var(--text)"><?=$p['period_number']?></span>
<span style="font-size:10px;color:var(--text3)"><?=$p['drawn_at']??$p['updated_at']??''?></span>
</div>
<div class="balls">
<?php for($i=1;$i<=10;$i++): $v=$p['rank_'.$i]??''; if(!$v) continue;
$colors=['','#e74c3c','#3498db','#2ecc71','#f39c12','#9b59b6','#1abc9c','#e67e22','#e91e63','#00bcd4','#8bc34a']; ?>
<div class="ball" style="background:<?=$colors[(int)$v]??'#999'?>"><?=$v?></div>
<?php endfor; ?>
</div>
<?php $sum=($p['rank_1']??0)+($p['rank_2']??0); if($sum): ?>
<div style="margin-top:4px;font-size:11px;color:var(--text3)"><?=$t('sum_label')?>: <span style="color:var(--primary);font-weight:600"><?=$sum?></span></div>
<?php endif; ?>
</div>
<?php endforeach; endif; ?>
</div>
<?php $navActive='lottery'; include __DIR__.'/_nav.php'; ?>
</body></html>
+350
View File
@@ -0,0 +1,350 @@
<?php use App\Core\I18n; I18n::init(); $t = function($k,$p=[]){return I18n::t($k,$p);}; $gameId = $game['id'] ?? 0; ?>
<!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">
<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}
.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}
/* 顶部栏 */
.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}
.pk-header .stats-btn{color:#333;font-size:14px;display:flex;align-items:center;gap:4px;text-decoration:none;cursor:pointer;background:none;border:none}
.pk-header .bal{color:var(--primary);font-weight:700;font-family:monospace;font-size:15px;display:flex;align-items:center;gap:4px}
/* 上期结果区 */
.result-bar{background:#fff;padding:10px 15px;border-bottom:1px solid #F0F0F0}
.result-balls{display:flex;gap:3px;flex-wrap:wrap;margin-top:4px}
.result-tags{display:flex;gap:3px;flex-wrap:wrap;margin-top:6px}
.result-tags span{display:inline-block;padding:2px 6px;border:1px solid #ddd;border-radius:4px;font-size:11px;color:#666}
/* 期号状态栏 */
.period-bar{background:#fff;padding:10px 15px;border-bottom:1px solid #F0F0F0;display:flex;align-items:center;justify-content:space-between}
.period-badge{font-size:13px;font-weight:600}
.period-badge.open{color:var(--success)}.period-badge.closed{color:var(--danger)}.period-badge.drawing{color:var(--warn)}
.seal-label{font-size:13px;font-weight:700;color:#333}
.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}
/* 左右布局 */
.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.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-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:first-child{border-right:1px solid #F5F5F5}
.bet-cell:active,.bet-cell.selected{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)}
.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}
</style>
</head>
<body class="pb-nav" style="background:#F5F5F5">
<!-- 顶部栏 -->
<header class="pk-header">
<div class="title">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#1E90FF" stroke-width="2"><circle cx="7" cy="17" r="2"/><circle cx="17" cy="17" r="2"/><path d="M5 17H3v-4l2-5h9l4 5h3v4h-2"/><path d="M5 12h14"/></svg>
<?=$t('pk10_title')?>
</div>
<button class="stats-btn" onclick="location.href='/game/pk10/stats'"><?=$t('statistics')?><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg></button>
<div class="bal">
<svg width="16" height="16" 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>
<span id="balanceDisplay"><?=number_format($user['balance']??0,2)?></span>
<select onchange="setLang(this.value)" style="padding:2px 4px;border:1px solid #ddd;border-radius:4px;font-size:10px;background:#fff;color:#666">
<?php foreach(I18n::LANGUAGES as $c=>$n): ?><option value="<?=$c?>" <?=I18n::getLang()===$c?'selected':''?>><?=$n?></option><?php endforeach; ?>
</select>
<?php if(!empty($isAgent)): ?><a href="/agent" style="color:#1E90FF;font-size:12px;text-decoration:none"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="8.5" cy="7" r="4"/><line x1="20" y1="8" x2="20" y2="14"/><line x1="23" y1="11" x2="17" y2="11"/></svg></a><?php endif; ?>
</div>
</header>
<!-- 上期结果区 -->
<div class="result-bar">
<div style="display:flex;align-items:center;gap:8px">
<span style="font-size:13px;font-weight:600;color:#333" id="lastPeriodNum">---</span>
<span style="font-size:11px;color:#999"><?=$t('last_result')?></span>
</div>
<div class="result-balls" id="lastResult"><span style="color:#999">---</span></div>
<div class="result-tags" id="lastResultTags"></div>
</div>
<!-- 当前期状态栏 -->
<div class="period-bar">
<div style="display:flex;align-items:center;gap:6px">
<span style="font-size:13px;font-weight:600;color:#333" id="periodNum">---</span>
<span class="seal-label"><?=$t('seal_plate')?></span>
<span id="statusBadge" class="seal-status"><?=$t('betting')?></span>
</div>
<div style="display:flex;align-items:center;gap:6px">
<span class="draw-label"><?=$t('draw_time')?></span>
<span class="draw-countdown" id="countdown">--</span>
</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; ?>
</div>
</div>
<!-- 左右布局投注面板 -->
<div id="betPanel">
<div class="bet-layout">
<!-- 左侧菜单 -->
<div class="side-menu">
<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>
<!-- 右侧投注内容 -->
<div class="bet-content">
<!-- 两面(大小+单双+龙虎)-->
<div id="panelBs">
<!-- 冠亚和 大小单双 -->
<div class="bet-section-title"><?=$t('sum')?></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>
<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>
<?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>
<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>
<?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>
<?php endif; endfor; ?>
</div>
<!-- 1~10名 号码投注 -->
<div id="panelRank" class="hidden" style="padding:10px">
<?php for($r=1;$r<=10;$r++): $label=$r<=2?($r==1?$t('champion'):$t('runner_up')):$t('rank_n',['n'=>$r]); ?>
<div style="margin-bottom:10px">
<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">
<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>
<?php endfor; ?>
</div>
</div>
<?php endfor; ?>
</div>
<!-- 冠亚和值 -->
<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): ?>
<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>
<?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>
</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>
</div>
<!-- 本期已下注 -->
<div style="background:#fff;padding:10px 15px;border-top:1px solid #F0F0F0" id="myBetsPanel">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px">
<span style="color:#999;font-size:12px"><?=$t('current_bets')?></span>
<span style="color:#999;font-size:11px" id="myBetsCount">0</span>
</div>
<div id="myBetsList" style="max-height:140px;overflow-y:auto">
<div style="color:#999;font-size:12px;text-align:center;padding:8px"><?=$t('no_data')?></div>
</div>
</div>
<!-- 上期结果面板 -->
<div class="hidden" style="background:#fff;padding:10px 15px;border-top:1px solid #F0F0F0" id="lastResultPanel">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px">
<span style="color:#999;font-size:12px"><?=$t('last_result_label')?></span>
<span style="font-size:12px;font-weight:700" id="lastProfitDisplay"></span>
</div>
<div id="lastMyBetsList" style="max-height:128px;overflow-y:auto"></div>
</div>
</div><!-- /betPanel -->
<!-- 历史记录 -->
<div style="background:#fff;margin-top:8px;padding:10px 15px;border-top:1px solid #F0F0F0">
<div style="color:#999;font-size:12px;margin-bottom:6px"><?=$t('history')?></div>
<div id="historyList" style="font-size:12px;max-height:200px;overflow-y:auto"></div>
</div>
<!-- 底部导航 -->
<?php $navActive='game'; include __DIR__.'/_nav.php'; ?>
<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);})();
</script>
</body></html>
+165
View File
@@ -0,0 +1,165 @@
<?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('statistics')?></title>
<link rel="stylesheet" href="/Static/css/app.css">
<style>
.stats-header{background:#fff;padding:12px 15px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #F0F0F0;position:sticky;top:0;z-index:10}
.stats-header .back{background:none;border:none;font-size:20px;color:#333;cursor:pointer;padding:0 8px 0 0}
.stats-tabs{display:flex;background:#fff;border-bottom:1px solid #F0F0F0}
.stats-tab{flex:1;text-align:center;padding:14px 0;font-size:15px;color:#666;cursor:pointer;position:relative;background:none;border:none}
.stats-tab.active{color:#333;font-weight:600}
.stats-tab.active::after{content:'';position:absolute;bottom:0;left:30%;right:30%;height:2px;background:var(--primary)}
.filter-card{background:#fff;margin:10px;border-radius:12px;padding:15px;box-shadow:0 1px 6px rgba(0,0,0,.06)}
.filter-title{text-align:center;color:var(--primary);font-size:14px;font-weight:600;margin-bottom:10px}
.tag-row{display:flex;flex-wrap:wrap;gap:8px;justify-content:center}
.tag{padding:6px 14px;border:1px solid #ddd;border-radius:20px;font-size:13px;color:#666;cursor:pointer;background:#fff;transition:all .2s}
.tag.active{border-color:var(--primary);color:var(--primary);background:#F0F8FF}
.ball-grid{display:flex;justify-content:center;gap:6px;flex-wrap:wrap;margin:15px 0}
.ball-col{display:flex;flex-direction:column;align-items:center;gap:4px}
.ball-col .count{font-size:11px;color:#999}
.bead-grid{display:grid;grid-template-columns:repeat(10,1fr);gap:2px;margin:10px;font-size:12px;text-align:center}
.bead-grid .cell{padding:6px 2px;color:#666;border-bottom:1px dotted #eee}
/* PLACEHOLDER_STREAK */
.streak-table{width:100%;border-collapse:collapse;margin:10px}
.streak-table th{background:var(--primary);color:#fff;padding:10px;font-size:14px}
.streak-table td{padding:10px 12px;border-bottom:1px solid #F0F0F0;font-size:14px;text-align:center}
.streak-table tr:nth-child(even){background:#FAFAFA}
.streak-badge{display:inline-block;padding:3px 10px;border-radius:4px;color:#fff;font-size:12px;font-weight:600}
</style>
</head>
<body class="pb-nav" style="background:#F5F5F5">
<div class="stats-header">
<button class="back" onclick="history.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></button>
<span style="font-weight:700;font-size:16px"><?=$t('statistics')?><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align:middle;margin-left:4px"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg></span>
<span style="width:28px"></span>
</div>
<div class="stats-tabs">
<button class="stats-tab active" onclick="showStatsTab('bead',this)"><?=$t('road_bead')?></button>
<button class="stats-tab" onclick="showStatsTab('streak',this)"><?=$t('two_side_streak')?></button>
</div>
<!-- 路珠 Tab -->
<div id="tabBead">
<?php
$rankLabels=[];
for($i=1;$i<=10;$i++) $rankLabels[$i]=$i<=2?($i==1?$t('champion'):$t('runner_up')):$t('rank_n',['n'=>$i]);
?>
<div class="filter-card">
<div class="filter-title"><?=$t('filter_rank')?></div>
<div class="tag-row" id="rankTags">
<?php for($i=1;$i<=10;$i++): ?>
<span class="tag <?=$i==1?'active':''?>" onclick="filterRank(<?=$i?>,this)"><?=$rankLabels[$i]?></span>
<?php endfor; ?>
</div>
</div>
<div class="filter-card">
<div class="ball-grid" id="ballStats"></div>
</div>
<div class="filter-card">
<div class="filter-title"><?=$t('filter_bead')?></div>
<div class="tag-row" id="beadTags">
<span class="tag active" onclick="filterBead('rank',this)"><?=$t('champion')?></span>
<span class="tag" onclick="filterBead('bs',this)"><?=$t('big')?>/<?=$t('small')?></span>
<span class="tag" onclick="filterBead('oe',this)"><?=$t('odd')?>/<?=$t('even')?></span>
<span class="tag" onclick="filterBead('sum',this)"><?=$t('sum')?></span>
<span class="tag" onclick="filterBead('sum_bs',this)"><?=$t('sum_bs_big')?>/<?=$t('sum_bs_small')?></span>
<span class="tag" onclick="filterBead('sum_oe',this)"><?=$t('sum_bs_odd')?>/<?=$t('sum_bs_even')?></span>
</div>
<div class="bead-grid" id="beadGrid"></div>
</div>
</div>
<!-- 两面长龙 Tab -->
<div id="tabStreak" class="hidden">
<div style="margin:10px;background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 1px 6px rgba(0,0,0,.06)">
<table class="streak-table">
<thead><tr><th><?=$t('rank_number')?></th><th><?=$t('periods_count')?></th></tr></thead>
<tbody id="streakBody"></tbody>
</table>
</div>
</div>
<?php $navActive='game'; include __DIR__.'/_nav.php'; ?>
<script>
const DATA=<?=json_encode(array_values($results??[]))?>;
const COLORS=['','#e74c3c','#3498db','#2ecc71','#f39c12','#9b59b6','#1abc9c','#e67e22','#e91e63','#00bcd4','#8bc34a'];
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';
function showStatsTab(id,btn){
document.getElementById('tabBead').classList.toggle('hidden',id!=='bead');
document.getElementById('tabStreak').classList.toggle('hidden',id!=='streak');
document.querySelectorAll('.stats-tab').forEach(t=>t.classList.remove('active'));
btn.classList.add('active');
if(id==='streak')renderStreak();
}
function filterRank(r,el){curRank=r;document.querySelectorAll('#rankTags .tag').forEach(t=>t.classList.remove('active'));el.classList.add('active');renderBalls();renderBead();}
function filterBead(type,el){curBead=type;document.querySelectorAll('#beadTags .tag').forEach(t=>t.classList.remove('active'));el.classList.add('active');renderBead();}
function renderBalls(){
const counts={};for(let i=1;i<=10;i++)counts[i]=0;
DATA.forEach(r=>{const v=+r['rank_'+curRank];if(v)counts[v]++;});
const el=document.getElementById('ballStats');
let h='';for(let i=1;i<=10;i++){h+='<div class="ball-col"><div class="car c'+i+'" style="width:32px;height:32px;font-size:13px">'+i+'</div><div class="count">'+counts[i]+'</div></div>';}
el.innerHTML=h;
}
function renderBead(){
const grid=document.getElementById('beadGrid');
const rows=DATA.slice(0,20);// last 20 periods, 10 cols each
let h='';
rows.forEach(r=>{
for(let c=1;c<=10;c++){
let val='';
if(curBead==='rank'){val=r['rank_'+c]||'';h+='<div class="cell" style="color:'+COLORS[+val||0]+'">'+val+'</div>';}
else if(curBead==='bs'){const v=+r['rank_'+c];val=v>5?I18N.big:I18N.small;h+='<div class="cell">'+val+'</div>';}
else if(curBead==='oe'){const v=+r['rank_'+c];val=v%2?I18N.odd:I18N.even;h+='<div class="cell">'+val+'</div>';}
else if(curBead==='sum'){const s=(+r.rank_1)+(+r.rank_2);h+='<div class="cell" style="grid-column:span 10;font-weight:600">'+s+'</div>';break;}
else if(curBead==='sum_bs'){const s=(+r.rank_1)+(+r.rank_2);val=s>=12?I18N.sum_big:I18N.sum_small;h+='<div class="cell" style="grid-column:span 10">'+val+'</div>';break;}
else if(curBead==='sum_oe'){const s=(+r.rank_1)+(+r.rank_2);val=s%2?I18N.sum_odd:I18N.sum_even;h+='<div class="cell" style="grid-column:span 10">'+val+'</div>';break;}
}
});
grid.innerHTML=h;
}
function renderStreak(){
// 计算两面长龙:每个名次的大小/单双/龙虎连续出现次数
const streaks=[];
for(let r=1;r<=10;r++){
if(DATA.length<2)continue;
const v0=+DATA[0]['rank_'+r];
// 大小
const bs0=v0>5?'big':'small';let bsCnt=1;
for(let i=1;i<DATA.length;i++){const v=+DATA[i]['rank_'+r];if((v>5?'big':'small')===bs0)bsCnt++;else break;}
if(bsCnt>=2)streaks.push({rank:rankLabels[r-1],type:bs0==='big'?I18N.big:I18N.small,color:bs0==='big'?'#e74c3c':'#3498db',count:bsCnt});
// 单双
const oe0=v0%2?'odd':'even';let oeCnt=1;
for(let i=1;i<DATA.length;i++){const v=+DATA[i]['rank_'+r];if((v%2?'odd':'even')===oe0)oeCnt++;else break;}
if(oeCnt>=2)streaks.push({rank:rankLabels[r-1],type:oe0==='odd'?I18N.odd:I18N.even,color:oe0==='odd'?'#f39c12':'#9b59b6',count:oeCnt});
// 龙虎 (rank 1-5)
if(r<=5){const a=+DATA[0]['rank_'+r],b=+DATA[0]['rank_'+(11-r)];const dt0=a>b?'dragon':'tiger';let dtCnt=1;
for(let i=1;i<DATA.length;i++){const a2=+DATA[i]['rank_'+r],b2=+DATA[i]['rank_'+(11-r)];if((a2>b2?'dragon':'tiger')===dt0)dtCnt++;else break;}
if(dtCnt>=2)streaks.push({rank:rankLabels[r-1],type:dt0==='dragon'?I18N.dragon:I18N.tiger,color:dt0==='dragon'?'#e74c3c':'#3498db',count:dtCnt});}
}
// 冠亚和大小单双
if(DATA.length>=2){
const s0=(+DATA[0].rank_1)+(+DATA[0].rank_2);
const sbs0=s0>=12?'big':'small';let sbsCnt=1;
for(let i=1;i<DATA.length;i++){const s=(+DATA[i].rank_1)+(+DATA[i].rank_2);if((s>=12?'big':'small')===sbs0)sbsCnt++;else break;}
if(sbsCnt>=2)streaks.push({rank:I18N.sum_big.replace(I18N.big,'').trim()||'<?=$t('sum')?>',type:sbs0==='big'?I18N.sum_big:I18N.sum_small,color:sbs0==='big'?'#e74c3c':'#3498db',count:sbsCnt});
const soe0=s0%2?'odd':'even';let soeCnt=1;
for(let i=1;i<DATA.length;i++){const s=(+DATA[i].rank_1)+(+DATA[i].rank_2);if((s%2?'odd':'even')===soe0)soeCnt++;else break;}
if(soeCnt>=2)streaks.push({rank:'<?=$t('sum_bs_tab')?>',type:soe0==='odd'?I18N.sum_odd:I18N.sum_even,color:soe0==='odd'?'#f39c12':'#9b59b6',count:soeCnt});
}
streaks.sort((a,b)=>b.count-a.count);
const body=document.getElementById('streakBody');
body.innerHTML=streaks.map(s=>'<tr><td style="text-align:left;color:var(--primary)">'+s.rank+' <span class="streak-badge" style="background:'+s.color+'">'+s.type+'</span></td><td style="font-weight:600">'+s.count+'</td></tr>').join('');
}
renderBalls();renderBead();
</script>
</body></html>
+147
View File
@@ -0,0 +1,147 @@
<?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('my_profile')?></title>
<link rel="stylesheet" href="/Static/css/app.css">
<style>
.header-bg{background:linear-gradient(135deg,#E8F4FF,#D0E8FF);padding:15px 15px 80px}
.top-toolbar{display:flex;justify-content:flex-end;gap:20px;margin-bottom:30px}
.top-toolbar .icon{cursor:pointer;opacity:.9}
.top-toolbar .icon:hover{opacity:1}
.user-info{display:flex;align-items:center;gap:15px}
.avatar-circle{width:60px;height:60px;border-radius:50%;border:3px solid #fff;box-shadow:0 2px 8px rgba(0,0,0,.1);background:#c8dff5;display:flex;align-items:center;justify-content:center}
.avatar-circle svg{width:32px;height:32px;color:#7ab0e0}
.username{flex:1;font-size:18px;font-weight:600;color:#333;display:flex;align-items:center;gap:5px}
.wallet-card{background:#fff;margin:-60px 15px 15px;padding:20px;border-radius:16px;box-shadow:0 2px 12px rgba(0,0,0,.08);position:relative;z-index:2}
.wallet-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:15px}
.wallet-title{font-size:16px;font-weight:600;color:#333}
.wallet-actions{display:flex;gap:20px}
.action-btn{display:flex;flex-direction:column;align-items:center;gap:4px;cursor:pointer;background:none;border:none;padding:0}
.action-btn span{font-size:12px;color:#1E90FF}
.balance-num{font-size:32px;font-weight:700;color:#333;margin:10px 0}
.section{background:#fff;margin:0 15px 15px;border-radius:12px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,.06)}
.section-title{font-size:16px;font-weight:600;color:#333;padding:15px 20px;border-bottom:1px solid #F5F5F5}
.menu-item{display:flex;align-items:center;padding:16px 20px;border-bottom:1px solid #F5F5F5;cursor:pointer;transition:background .2s}
.menu-item:last-child{border-bottom:none}
.menu-item:active{background:#F8F8F8}
.menu-icon{margin-right:15px;display:flex}
.menu-text{flex:1;font-size:15px;color:#333}
.menu-arrow{font-size:20px;color:#CCC}
</style>
</head>
<body style="background:#F5F5F5;padding-bottom:60px">
<!-- 顶部背景 -->
<div class="header-bg">
<div class="top-toolbar">
<a href="/logout" class="icon" title="<?=$t('logout')?>">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#666" stroke-width="2"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
</a>
</div>
<div class="user-info">
<div class="avatar-circle"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg></div>
<div class="username"><?=htmlspecialchars($user['username']??'')?><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#999" stroke-width="3"><polyline points="9 18 15 12 9 6"/></svg></div>
</div>
</div>
<!-- 钱包卡片 -->
<div class="wallet-card">
<div class="wallet-header">
<div class="wallet-title"><?=$t('balance')?></div>
<div class="wallet-actions">
<button class="action-btn" onclick="alert('<?=$t('deposit')?>')">
<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')?>')">
<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')?>')">
<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>
</div>
</div>
<div class="balance-num"><?=number_format($user['balance']??0,2)?></div>
</div>
<!-- USDT绑定 -->
<div class="section">
<div class="section-title"><?=$t('usdt_address')?></div>
<div style="padding:16px 20px">
<?php if(!empty($user['usdt_address'])): ?>
<div style="background:#F0FFF0;border-radius:8px;padding:10px;font-size:13px;font-family:monospace;color:#4CAF50;word-break:break-all"><?=htmlspecialchars($user['usdt_address'])?> (<?=$user['usdt_chain']?>)</div>
<?php else: ?>
<div style="display:flex;gap:8px">
<input type="text" id="usdtAddr" placeholder="T..." class="input" style="flex:1;font-size:13px">
<button onclick="bindUsdt()" class="btn btn-primary btn-sm"><?=$t('bind_address')?></button>
</div>
<?php endif; ?>
</div>
</div>
<!-- 投注记录 -->
<div class="section">
<div class="section-title"><?=$t('bet_history')?></div>
<?php
function formatBetDisplay($betType, $betValue, $t) {
if ($betType==='rank'&&preg_match('/^rank(\d+)_(\d+)$/',$betValue,$m)){$r=(int)$m[1];$rl=$r===1?$t('champion'):($r===2?$t('runner_up'):$t('rank_n',['n'=>$r]));return $rl.' #'.$m[2];}
if ($betType==='bs'&&preg_match('/^rank(\d+)_(big|small)$/',$betValue,$m)){$r=(int)$m[1];$rl=$r===1?$t('champion'):($r===2?$t('runner_up'):$t('rank_n',['n'=>$r]));return $rl.' '.($m[2]==='big'?$t('big'):$t('small'));}
if ($betType==='oe'&&preg_match('/^rank(\d+)_(odd|even)$/',$betValue,$m)){$r=(int)$m[1];$rl=$r===1?$t('champion'):($r===2?$t('runner_up'):$t('rank_n',['n'=>$r]));return $rl.' '.($m[2]==='odd'?$t('odd'):$t('even'));}
if ($betType==='dt'&&preg_match('/^dt(\d+)_(dragon|tiger)$/',$betValue,$m)){$ps=[1=>[1,10],2=>[2,9],3=>[3,8],4=>[4,7],5=>[5,6]];$p=$ps[(int)$m[1]]??[0,0];return $t('rank_n',['n'=>$p[0]]).'vs'.$t('rank_n',['n'=>$p[1]]).' '.($m[2]==='dragon'?$t('dragon'):$t('tiger'));}
if ($betType==='sum'&&preg_match('/^sum_(\d+)$/',$betValue,$m))return $t('sum').' '.$m[1];
if ($betType==='sum_bs'){$map=['sum_big'=>$t('sum_big'),'sum_small'=>$t('sum_small'),'sum_odd'=>$t('sum_odd'),'sum_even'=>$t('sum_even')];return $map[$betValue]??$betValue;}
return $betType.':'.$betValue;
}
?>
<?php if(empty($bets)): ?>
<div style="text-align:center;color:#999;padding:40px 20px;font-size:14px"><?=$t('no_data')?></div>
<?php else: foreach($bets as $b): ?>
<div class="menu-item" style="cursor:default">
<div style="flex:1;min-width:0">
<div style="font-size:14px;color:#333"><?=htmlspecialchars(formatBetDisplay($b['bet_type'],$b['bet_value'],$t))?></div>
<div style="font-size:11px;color:#999;margin-top:2px"><?=$b['period_number']?> · <?=$b['created_at']?></div>
</div>
<div style="text-align:right">
<div style="font-size:14px;color:#333"><?=number_format($b['amount'],2)?> x<?=$b['odds']?></div>
<span style="font-size:12px;color:<?=$b['status']==='win'?'#4CAF50':($b['status']==='lose'?'#FF4444':'#FF9800')?>;font-weight:600"><?=$t($b['status'])?></span>
</div>
</div>
<?php endforeach; endif; ?>
</div>
<!-- 交易记录 -->
<div class="section">
<div class="section-title"><?=$t('transaction_history')?></div>
<?php if(empty($transactions)): ?>
<div style="text-align:center;color:#999;padding:40px 20px;font-size:14px"><?=$t('no_data')?></div>
<?php else: foreach($transactions as $tx): $isPos=(float)$tx['amount']>0; ?>
<div class="menu-item" style="cursor:default">
<div style="flex:1"><div style="font-size:14px;color:#333"><?=$tx['type']?></div><div style="font-size:11px;color:#999;margin-top:2px"><?=$tx['created_at']?></div></div>
<span style="font-size:15px;font-weight:600;color:<?=$isPos?'#4CAF50':'#FF4444'?>"><?=$isPos?'+':''?><?=number_format($tx['amount'],2)?></span>
</div>
<?php endforeach; endif; ?>
</div>
<!-- 其他 -->
<div class="section">
<div class="section-title"><?=$t('other')?></div>
<a href="/details" class="menu-item" style="text-decoration:none">
<div class="menu-icon"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#666" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg></div>
<div class="menu-text"><?=$t('bet_details')?></div>
<div class="menu-arrow"></div>
</a>
<a href="/lottery" class="menu-item" style="text-decoration:none">
<div class="menu-icon"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#666" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg></div>
<div class="menu-text"><?=$t('lottery_history')?></div>
<div class="menu-arrow"></div>
</a>
</div>
<?php $navActive='profile'; include __DIR__.'/_nav.php'; ?>
<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();}
</script>
</body></html>
+424
View File
@@ -0,0 +1,424 @@
<html lang="vi">
<?php
// Mock Odds for Xoc Dia (Static Preview)
$odds = [
'chan' => 0.96,
'le' => 0.96,
'4red' => 12,
'4white' => 12,
'3red1white' => 2.6,
'3white1red' => 2.6
];
?>
<head>
<title>TM68 - Xóc Đĩa Live</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<!-- Reuse existing styles -->
<link rel="stylesheet" href="/Static/css/style.css">
<link rel="stylesheet" href="/Static/ckplayer/css/ckplayer.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/Static/layui/css/layui.css">
<style>
/* 隐藏ckplayer控制条 */
.ckplayer-ckplayer .ck-bar, .ckplayer-ckplayer .ck-center-play, .ckplayer-ckplayer .ck-controls {
display: none !important;
}
/* Xoc Dia Specific Styles */
.xocdia-top-row {
display: flex;
gap: 12px;
margin-bottom: 12px;
min-height: 100px;
}
.xocdia-big-btn {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 12px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: transform 0.2s, box-shadow 0.2s;
border: 2px solid rgba(255,255,255,0.1);
}
.xocdia-big-btn:hover {
transform: translateY(-2px);
filter: brightness(1.1);
}
.xocdia-big-btn.bet-chan {
background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 100%);
box-shadow: 0 4px 15px rgba(30, 58, 138, 0.4);
}
.xocdia-big-btn.bet-le {
background: linear-gradient(135deg, #b91c1c 0%, #991b1b 100%);
box-shadow: 0 4px 15px rgba(185, 28, 28, 0.4);
}
.xocdia-title {
font-size: 28px;
font-weight: 900;
color: #fff;
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
margin-bottom: 4px;
letter-spacing: 1px;
}
.xocdia-subtitle {
font-size: 11px;
color: rgba(255,255,255,0.8);
margin-bottom: 4px;
}
.xocdia-odds {
background: rgba(0,0,0,0.3);
padding: 2px 10px;
border-radius: 12px;
font-size: 12px;
font-weight: 700;
color: #ffd700;
border: 1px solid rgba(255,215,0,0.3);
}
.xocdia-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
margin-bottom: 15px;
}
.xocdia-cell {
background: linear-gradient(135deg, #ffffff 0%, #f1f5f9 100%);
border: 1px solid #cbd5e1;
border-radius: 12px;
padding: 8px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
cursor: pointer;
transition: all 0.2s;
min-height: 80px;
position: relative;
}
.xocdia-cell:hover {
transform: translateY(-2px);
border-color: #3b82f6;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.2);
}
.xocdia-dots {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
width: 28px;
}
.dot {
width: 12px;
height: 12px;
border-radius: 50%;
display: block;
box-shadow: inset 0 -1px 2px rgba(0,0,0,0.2);
}
.dot.red {
background-color: #ef4444;
border: 1px solid #dc2626;
}
.dot.white {
background-color: #fff;
border: 1px solid #cbd5e1;
}
.xocdia-label {
font-size: 11px;
font-weight: 800;
color: #334155;
text-align: center;
line-height: 1.2;
}
.xocdia-cell .bet-odds-mini {
margin-top: 0;
background: rgba(0,0,0,0.05);
color: #64748b;
}
/* Mobile Responsive Adjustments */
@media (max-width: 768px) {
.xocdia-grid {
grid-template-columns: repeat(2, 1fr);
}
.xocdia-top-row {
min-height: 80px;
}
}
/* PC Specific Adjustments matching live.php style */
@media (min-width: 769px) {
html, body {
background: #000;
overflow: hidden;
height: 100%;
}
.live-wrapper {
height: 100vh;
max-width: 1600px;
margin: 0 auto;
padding: 20px;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.live-layout {
display: grid;
grid-template-columns: 1.5fr 1fr;
gap: 20px;
flex: 1;
min-height: 0;
}
.bet-panel-v2 {
height: 100%;
overflow-y: auto;
background: rgba(255, 255, 255, 0.98);
border-radius: 16px;
padding: 20px;
}
.xocdia-top-row {
min-height: 120px;
}
.xocdia-grid {
grid-template-columns: repeat(4, 1fr);
}
}
</style>
</head>
<body class="live-body" style="background-color: black;">
<img id="bg-img" src="/Static/tm68/background.png" alt="Background" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; z-index: -1;">
<div class="live-wrapper">
<!-- Top Bar (Reused) -->
<div class="live-topbar">
<div class="live-topbar-left">
<button class="live-circle-btn" type="button" onclick="window.location.href='/'"><i class="fa-solid fa-arrow-left"></i></button>
<div class="live-balance">
<div class="live-balance-coin"><i class="fa-solid fa-dollar-sign"></i></div>
<div class="live-balance-text">
<div class="live-balance-id">ID: <?= htmlspecialchars($user['username'] ?? 'User') ?></div>
<div class="live-balance-amount"><?= number_format(($user['balance'] ?? 0) / 1000, 0, ',', '.') ?>k</div>
</div>
<button class="live-circle-btn live-refresh"><i class="fa-solid fa-rotate-right"></i></button>
</div>
</div>
<div class="live-topbar-right">
<button class="lobby-icon-btn"><i class="fa-solid fa-user"></i></button>
<button class="lobby-icon-btn"><i class="fa-solid fa-bars"></i></button>
</div>
</div>
<div class="live-layout live-layout-v2">
<!-- Left: Video Stream -->
<div class="live-left">
<div class="live-panel live-stream live-stream-v2" style="background: #000; position: relative;">
<div class="live-stream-main">
<!-- Placeholder for CKPlayer -->
<div id="tm68LivePlayer" class="live-player" data-stream="<?= htmlspecialchars($streamUrl ?? '') ?>"></div>
<div class="live-player-info" style="position: absolute; bottom: 10px; right: 10px; color: #fff; text-align: right; z-index: 10;">
<div class="live-player-period">G1202512200001</div>
<div class="live-player-datetime">Loading...</div>
</div>
</div>
</div>
<!-- Roadmap (Optional for Xoc Dia, simplified) -->
<div class="live-panel live-history" style="margin-top: 15px; background: rgba(255,255,255,0.9); padding: 10px; border-radius: 12px; height: 150px;">
<div style="font-size: 12px; font-weight: bold; color: #333; margin-bottom: 5px;">Lịch sử cầu</div>
<div style="display: flex; gap: 5px; flex-wrap: wrap;">
<!-- Mock History Dots -->
<span class="dot red"></span><span class="dot white"></span><span class="dot red"></span><span class="dot red"></span>
<span class="dot white"></span><span class="dot white"></span><span class="dot red"></span><span class="dot white"></span>
</div>
</div>
</div>
<!-- Right: Xoc Dia Betting Panel -->
<div class="live-panel bet-panel bet-panel-v2">
<!-- Overlay for Countdown/Lock -->
<div id="betOverlay" class="bet-overlay hidden" style="position: absolute; inset: 0; background: rgba(0,0,0,0.6); z-index: 50; display: none; align-items: center; justify-content: center; flex-direction: column; border-radius: 16px;">
<div id="statusDisplay" class="status-text" style="color: #fff; font-size: 24px; font-weight: bold;">ĐANG TẢI...</div>
</div>
<!-- Timer -->
<div id="activeCountdown" style="text-align: right; margin-bottom: 10px; font-weight: bold; color: #333;">
<i class="fa-regular fa-clock"></i> <span id="activeTimer" style="color: #d93025; font-size: 18px;">60</span>s
</div>
<!-- Main Bets: Chan / Le -->
<div class="xocdia-top-row">
<div class="xocdia-big-btn bet-chan" data-bet-type="chan" data-odds="1:<?= $odds['chan'] ?>">
<div class="xocdia-title">CHẴN</div>
<div class="xocdia-subtitle">4 Đỏ, 4 Trắng, 2 Đỏ 2 Trắng</div>
<div class="xocdia-odds">1:<?= $odds['chan'] ?></div>
<div class="bet-amount-display" style="display:none"></div>
</div>
<div class="xocdia-big-btn bet-le" data-bet-type="le" data-odds="1:<?= $odds['le'] ?>">
<div class="xocdia-title">LẺ</div>
<div class="xocdia-subtitle">3 Đỏ 1 Trắng, 3 Trắng 1 Đỏ</div>
<div class="xocdia-odds">1:<?= $odds['le'] ?></div>
<div class="bet-amount-display" style="display:none"></div>
</div>
</div>
<!-- Grid Bets: Specific Colors -->
<div class="xocdia-grid">
<div class="xocdia-cell" data-bet-type="4red" data-odds="1:<?= $odds['4red'] ?>">
<div class="xocdia-dots"><span class="dot red"></span><span class="dot red"></span><span class="dot red"></span><span class="dot red"></span></div>
<div class="xocdia-label">4 ĐỎ</div>
<div class="bet-odds-mini">1:<?= $odds['4red'] ?></div>
<div class="bet-amount-display" style="display:none"></div>
</div>
<div class="xocdia-cell" data-bet-type="3red1white" data-odds="1:<?= $odds['3red1white'] ?>">
<div class="xocdia-dots"><span class="dot red"></span><span class="dot red"></span><span class="dot red"></span><span class="dot white"></span></div>
<div class="xocdia-label">3 ĐỎ 1 TRẮNG</div>
<div class="bet-odds-mini">1:<?= $odds['3red1white'] ?></div>
<div class="bet-amount-display" style="display:none"></div>
</div>
<div class="xocdia-cell" data-bet-type="3white1red" data-odds="1:<?= $odds['3white1red'] ?>">
<div class="xocdia-dots"><span class="dot white"></span><span class="dot white"></span><span class="dot white"></span><span class="dot red"></span></div>
<div class="xocdia-label">3 TRẮNG 1 ĐỎ</div>
<div class="bet-odds-mini">1:<?= $odds['3white1red'] ?></div>
<div class="bet-amount-display" style="display:none"></div>
</div>
<div class="xocdia-cell" data-bet-type="4white" data-odds="1:<?= $odds['4white'] ?>">
<div class="xocdia-dots"><span class="dot white"></span><span class="dot white"></span><span class="dot white"></span><span class="dot white"></span></div>
<div class="xocdia-label">4 TRẮNG</div>
<div class="bet-odds-mini">1:<?= $odds['4white'] ?></div>
<div class="bet-amount-display" style="display:none"></div>
</div>
</div>
<!-- Actions -->
<div class="bet-actions bet-actions-v2">
<button class="bet-action bet-action-green" type="button" id="confirmBetBtn">
<i class="fa-solid fa-check"></i> <span>ĐẶT CƯỢC</span>
</button>
<button class="bet-action bet-action-red" type="button" id="cancelBetBtn">
<i class="fa-solid fa-xmark"></i> <span>HỦY</span>
</button>
</div>
<!-- Chips -->
<div class="bet-chips bet-chips-v2">
<button class="bet-chip-btn" data-amount="100000" data-label="100K"><div class="bet-chip-display chip-red">100K</div></button>
<button class="bet-chip-btn" data-amount="500000" data-label="500K"><div class="bet-chip-display chip-blue">500K</div></button>
<button class="bet-chip-btn" data-amount="1000000" data-label="1M"><div class="bet-chip-display chip-gold">1M</div></button>
<button class="bet-chip-btn" data-amount="5000000" data-label="5M"><div class="bet-chip-display chip-green">5M</div></button>
</div>
</div>
</div>
</div>
<script src="/Static/layui/layui.js"></script>
<script src="/Static/ckplayer/js/ckplayer.js"></script>
<script>
layui.use(['layer'], function(){
var layer = layui.layer;
var betState = { selectedChip: null, bets: {} };
// --- Chip Selection ---
document.querySelectorAll('.bet-chip-btn').forEach(btn => {
btn.addEventListener('click', function() {
document.querySelectorAll('.bet-chip-btn').forEach(b => b.classList.remove('chip-selected'));
this.classList.add('chip-selected');
var amount = this.getAttribute('data-amount');
var color = this.querySelector('.bet-chip-display').className.match(/chip-(\w+)/)[1];
betState.selectedChip = { amount: parseInt(amount), color: color };
});
});
// --- Bet Placement ---
document.querySelectorAll('.xocdia-big-btn, .xocdia-cell').forEach(el => {
el.addEventListener('click', function() {
if(!betState.selectedChip) {
layer.msg('Vui lòng chọn chip (số tiền) trước!');
return;
}
var type = this.getAttribute('data-bet-type');
var odds = this.getAttribute('data-odds');
// Add to bet state
if(!betState.bets[type]) {
betState.bets[type] = { amount: 0, odds: odds };
}
betState.bets[type].amount += betState.selectedChip.amount;
// Visual Update
updateBetVisual(this, betState.bets[type].amount, betState.selectedChip.color);
this.classList.add('bet-selected');
});
});
function updateBetVisual(el, totalAmount, color) {
var display = el.querySelector('.bet-amount-display');
display.style.display = 'flex';
display.innerHTML = `
<div class="bet-chip-wrapper">
<div class="bet-chip-icon chip-${color}"></div>
</div>
<div class="bet-amount-text">${formatAmount(totalAmount)}</div>
`;
}
function formatAmount(n) {
if(n >= 1000000) return (n/1000000) + 'M';
if(n >= 1000) return (n/1000) + 'K';
return n;
}
// --- Actions ---
document.getElementById('cancelBetBtn').addEventListener('click', function() {
betState.bets = {};
document.querySelectorAll('.bet-amount-display').forEach(d => {
d.innerHTML = '';
d.style.display = 'none';
});
document.querySelectorAll('.bet-selected').forEach(e => e.classList.remove('bet-selected'));
});
document.getElementById('confirmBetBtn').addEventListener('click', function() {
var keys = Object.keys(betState.bets);
if(keys.length === 0) return;
layer.msg('Đang gửi cược...', {icon: 16, shade: 0.01});
// Simulation of API Call
setTimeout(() => {
layer.msg('Đặt cược thành công!', {icon: 1});
// Reset visual but maybe keep state until confirmed by server in real app
document.getElementById('cancelBetBtn').click();
}, 1000);
});
// --- Clock Simulation ---
setInterval(() => {
var d = new Date();
document.querySelector('.live-player-datetime').innerText = d.toLocaleTimeString();
}, 1000);
});
</script>
</body>
</html>