This commit is contained in:
Flatlogic Bot 2026-02-13 15:48:44 +00:00
parent d6a987a3fc
commit 1fcaec3d09
7 changed files with 851 additions and 983 deletions

View File

@ -20,7 +20,7 @@ $pdo = db();
// --- Settlement Logic for Due Orders ---
try {
$now = date('Y-m-d H:i:s');
// Fetch orders that are pending and due for settlement, and join with users to get control settings.
// Fetch orders that are pending and due for settlement
$stmt = $pdo->prepare(
"SELECT o.*, u.win_loss_control FROM option_orders o " .
"JOIN users u ON o.user_id = u.id " .
@ -33,44 +33,38 @@ try {
$pdo->beginTransaction();
foreach ($due_orders as $order) {
$result = 'loss'; // Default to loss
$result = 'loss';
// Determine final control setting (user's setting is the only one we consider as per request)
$final_control = $order['win_loss_control'];
if ($final_control == 'win') {
$result = 'win';
} elseif ($final_control == 'loss') {
$result = 'loss';
} else { // No control set, default to random
$result = (rand(0, 100) < 51) ? 'loss' : 'win'; // 51% chance to lose to have a house edge
} else {
$result = (rand(0, 100) < 51) ? 'loss' : 'win';
}
$profit = 0;
// Calculate profit and update user balance
if ($result === 'win') {
$profit = $order['amount'] * $order['profit_rate'];
$total_return = $order['amount'] + $profit;
// Credit the user's balance with the principal + profit
$bal_stmt = $pdo->prepare("UPDATE users SET balance = balance + ? WHERE id = ?");
$bal_stmt->execute([$total_return, $order['user_id']]);
} else {
// Loss means the wagered amount is lost. No balance update needed as it was deducted at placement.
$profit = -$order['amount'];
}
// Fake a closing price that realistically matches the outcome
$price_variation = (float)($order['open_price'] * 0.0001 * rand(1, 5));
$price_variation = (float)($order['opening_price'] * 0.0001 * rand(1, 5));
if (($order['direction'] === 'up' && $result === 'win') || ($order['direction'] === 'down' && $result === 'loss')) {
$closing_price = $order['open_price'] + $price_variation;
$closing_price = $order['opening_price'] + $price_variation;
} else {
$closing_price = $order['open_price'] - $price_variation;
$closing_price = $order['opening_price'] - $price_variation;
}
// Update the order to settled status
$update_stmt = $pdo->prepare(
"UPDATE option_orders SET status = 'completed', result = ?, profit = ?, close_price = ? WHERE id = ?"
"UPDATE option_orders SET status = 'completed', result = ?, profit = ?, closing_price = ? WHERE id = ?"
);
$update_stmt->execute([$result, $profit, $closing_price, $order['id']]);
}
@ -81,13 +75,12 @@ try {
$pdo->rollBack();
}
error_log("Option settlement failed: " . $e->getMessage());
// Don't exit, still try to fetch orders for the user
}
// --- Fetch and Return Orders for Frontend ---
$stmt = $pdo->prepare("SELECT * FROM option_orders WHERE user_id = ? AND status = ? ORDER BY start_time DESC LIMIT 50");
$stmt = $pdo->prepare("SELECT * FROM option_orders WHERE user_id = ? AND status = ? ORDER BY created_at DESC LIMIT 50");
$stmt->execute([$user_id, $status_filter]);
$orders = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($orders);
?>
?>

View File

@ -17,6 +17,7 @@ $amount = (float)($data['amount'] ?? 0);
$direction = $data['direction'] ?? null;
$duration = (int)($data['duration'] ?? 0);
$rate = (float)($data['rate'] ?? 0); // Profit rate in percentage
$frontend_price = isset($data['price']) ? (float)$data['price'] : null;
if (empty($pair) || empty($direction) || $amount <= 0 || $duration <= 0 || $rate <= 0) {
echo json_encode(['success' => false, 'error' => 'Invalid order parameters.']);
@ -61,9 +62,15 @@ function get_current_price($symbol) {
}
$open_price = get_current_price($pair);
// Fallback to frontend price if server fetch fails
if ($open_price === null) {
echo json_encode(['success' => false, 'error' => 'Could not fetch current price for the pair. Please try again.']);
exit;
if ($frontend_price !== null && $frontend_price > 0) {
$open_price = $frontend_price;
} else {
echo json_encode(['success' => false, 'error' => 'Could not fetch current price for the pair. Please try again.']);
exit;
}
}
// --- Database Transaction ---
@ -90,10 +97,10 @@ try {
// 3. Insert the new option order
$start_time = date('Y-m-d H:i:s');
$settle_at = date('Y-m-d H:i:s', time() + $duration);
$profit_rate_decimal = $rate / 100; // Store as decimal for calculation
$profit_rate_decimal = $rate / 100;
$stmt = $pdo->prepare(
"INSERT INTO option_orders (user_id, pair, amount, direction, duration, profit_rate, open_price, status, start_time, settle_at) " .
"INSERT INTO option_orders (user_id, symbol, amount, direction, duration, profit_rate, opening_price, status, created_at, settle_at) " .
"VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?)"
);
$stmt->execute([$user_id, $pair, $amount, $direction, $duration, $profit_rate_decimal, $open_price, $start_time, $settle_at]);
@ -115,6 +122,6 @@ try {
$pdo->rollBack();
}
error_log("Option order failed: " . $e->getMessage());
echo json_encode(['success' => false, 'error' => 'An internal error occurred. Please try again later.']);
echo json_encode(['success' => false, 'error' => 'An internal error occurred.']);
}
?>

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View File

@ -1,153 +1,132 @@
<?php
include 'header.php';
require_once 'db/config.php';
require_once 'includes/i18n.php';
$user_id = $_SESSION['user_id'] ?? null;
$user_assets = [];
if ($user_id) {
$pdo = db();
$stmt = $pdo->prepare("SELECT currency, balance FROM assets WHERE user_id = ?");
$stmt = $db->prepare("SELECT balance FROM users WHERE id = ?");
$stmt->execute([$user_id]);
$usdt_balance = $stmt->fetchColumn();
$user_assets['USDT'] = (float)($usdt_balance ?: 0);
$stmt = $db->prepare("SELECT symbol, amount FROM user_assets WHERE user_id = ?");
$stmt->execute([$user_id]);
$assets_data = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
if ($assets_data) {
$user_assets = $assets_data;
foreach ($assets_data as $symbol => $amount) {
$user_assets[$symbol] = (float)$amount;
}
}
}
?>
<style>
/* New Robust Layout from spot.php */
html, body {
/* UNLOCKED for scrolling */
}
.trading-page-wrapper {
display: flex;
flex-direction: column;
min-height: 100vh; /* Ensure it takes at least full height */
}
.trading-page-wrapper .navbar {
flex-shrink: 0;
}
.trading-container {
flex-grow: 1;
display: flex;
min-height: 800px; /* Give a minimum height for content */
background: #0b0e11;
}
.trading-page-wrapper { display: flex; flex-direction: column; min-height: 100vh; background: #0b0e11; }
.trading-container { flex-grow: 1; display: flex; min-height: 800px; max-width: 1920px; margin: 0 auto; width: 100%; }
.left-col { width: 280px; flex-shrink: 0; background: #161a1e; border-right: 1px solid #2b3139; display: flex; flex-direction: column; }
.center-col { flex: 1; min-width: 600px; display: flex; flex-direction: column; border-right: 1px solid #2b3139; }
.right-col { width: 300px; flex-shrink: 0; background: #161a1e; display: flex; flex-direction: column; }
/* Columns */
.left-col { width: 260px; flex-shrink: 0; background: #161a1e; display: flex; flex-direction: column; border-right: 1px solid #2b3139; }
.center-col { flex: 1; min-width: 600px; display: flex; flex-direction: column; border-right: 1px solid #2b3139; min-height: 0; }
.right-col { width: 320px; flex-shrink: 0; background: #161a1e; display: flex; flex-direction: column; }
.chart-header { height: 55px; padding: 0 20px; display: flex; align-items: center; background: #161a1e; border-bottom: 1px solid #2b3139; gap: 20px; flex-shrink: 0; }
.chart-box { height: 450px; border-bottom: 1px solid #2b3139; flex-shrink: 0; }
.bottom-content-wrapper { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
/* Center Column Content Layout */
.chart-header { flex-shrink: 0; height: 50px; padding: 8px 20px; display: flex; align-items: center; background: #161a1e; border-bottom: 1px solid #2b3139; gap: 20px; }
.chart-box { height: 350px; flex-shrink: 0; border-bottom: 1px solid #2b3139; }
.bottom-content-wrapper { flex-grow: 1; overflow-y: auto; min-height: 0; }
.order-box { padding: 12px 20px; background: #161a1e; border-bottom: 1px solid #2b3139; }
.records-container { display: flex; flex-direction: column; background: #161a1e; flex-grow: 1; min-height: 400px; }
.record-tabs { display: flex; padding: 0 20px; flex-shrink: 0; border-bottom: 1px solid #2b3139; }
.record-tab { padding: 12px 0; margin-right: 25px; font-size: 14px; font-weight: 600; color: #848e9c; cursor: pointer; border-bottom: 2px solid transparent; }
.record-tab.active { color: white; border-bottom-color: var(--primary-color); }
#records-list { padding: 10px 0; flex-grow: 1; overflow-y: auto; }
.order-box { padding: 20px; background: #161a1e; border-bottom: 1px solid #2b3139; flex-shrink: 0; }
.records-container { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
.record-tabs { display: flex; padding: 0 20px; border-bottom: 1px solid #2b3139; flex-shrink: 0; }
.record-tab { padding: 12px 0; margin-right: 25px; font-size: 13px; font-weight: 600; color: #848e9c; cursor: pointer; position: relative; }
.record-tab.active { color: white; }
.record-tab.active::after { content: ''; position: absolute; bottom: 0; left: 0; width: 100%; height: 2px; background: var(--primary-color); }
.right-col { display: flex; flex-direction: column;}
.ob-panel, .trades-panel { flex-grow: 1; min-height: 0; display: flex; flex-direction: column;}
.ob-content { flex: 1; min-height: 0; display: flex; flex-direction: column; }
#asks-list { flex: 1 1 50%; display: flex; flex-direction: column; overflow: hidden; justify-content: flex-end; }
#bids-list { flex: 1 1 50%; display: flex; flex-direction: column; overflow: hidden; justify-content: flex-start; }
#trades-content { flex: 1; overflow-y: auto; min-height: 0; }
.left-col { overflow-y: auto; }
#pairs-list { flex: 1; overflow-y: auto; }
.ob-header, .trades-header { padding: 10px 15px; font-size: 12px; color: #848e9c; display: flex; justify-content: space-between; border-bottom: 1px solid #2b3139; background: #161a1e; flex-shrink: 0;}
.ob-row, .trade-row { display: flex; justify-content: space-between; padding: 3px 15px; font-size: 12px; position: relative; cursor: pointer; height: 20px; align-items: center; flex-shrink: 0; }
.ob-bar { position: absolute; right: 0; top: 0; bottom: 0; opacity: 0.1; z-index: 0; transition: width 0.1s linear; }
#mid-price { padding: 8px 15px; font-size: 16px; font-weight: 800; text-align: center; border-top: 1px solid #2b3139; border-bottom: 1px solid #2b3139; background: #161a1e; flex-shrink: 0;}
.coin-icon { width: 22px; height: 22px; margin-right: 12px; border-radius: 50%; }
.stats-item { display: flex; flex-direction: column; justify-content: center; }
.stats-label { font-size: 10px; color: #848e9c; margin-bottom: 1px; }
.stats-value { font-size: 12px; font-weight: 600; color: white; }
#websocket-status { display: none; /* Hiding this element as requested */ padding: 8px 15px; font-size: 12px; color: #fff; text-align: center; position: absolute; top: 0; left: 0; right: 0; z-index: 1000; }
#records-list-container { height: 350px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #2b3139 transparent; }
#records-list-container::-webkit-scrollbar { width: 4px; }
#records-list-container::-webkit-scrollbar-thumb { background: #2b3139; border-radius: 10px; }
table { width: 100%; font-size: 12px; border-collapse: collapse; }
th { color: #848e9c; font-weight: 500; text-align: left; padding: 10px 15px; border-bottom: 1px solid #2b3139; background: #161a1e; position: sticky; top: 0; z-index: 10; }
td { padding: 10px 15px; border-bottom: 1px solid rgba(255,255,255,0.02); color: #eaecef; height: 38px; }
.category-tabs { display: flex; padding: 15px 15px 5px; gap: 8px; }
.category-tab { flex: 1; text-align: center; padding: 6px 0; background: #2b3139; border-radius: 4px; font-size: 12px; color: #848e9c; cursor: pointer; transition: all 0.2s; border: 1px solid transparent; }
.category-tab { flex: 1; text-align: center; padding: 6px 0; background: #2b3139; border-radius: 4px; font-size: 12px; color: #848e9c; cursor: pointer; border: 1px solid transparent; }
.category-tab.active { background: rgba(0, 82, 255, 0.1); border-color: var(--primary-color); color: var(--primary-color); font-weight: bold; }
.search-box { padding: 5px 15px 10px; position: relative; }
.search-box i { position: absolute; left: 25px; top: 50%; transform: translateY(-50%); color: #848e9c; font-size: 12px; }
.search-box input { width: 100%; background: #2b3139; border: 1px solid transparent; border-radius: 4px; padding: 8px 10px 8px 30px; color: white; font-size: 13px; outline: none; }
.pair-item { display: flex; align-items: center; padding: 10px 15px; cursor: pointer; transition: background 0.2s; border-bottom: 1px solid rgba(255,255,255,0.02); }
.pair-item.active { background: #1e2329; }
.input-group { background: #2b3139; border-radius: 4px; padding: 6px 12px; display: flex; align-items: center; margin-bottom: 6px; border: 1px solid transparent; }
.input-group:focus-within { border-color: var(--primary-color); }
.input-group input { background: transparent; border: none; color: white; flex: 1; outline: none; font-size: 13px; width: 100%; }
.input-group .label { color: #848e9c; font-size: 11px; margin-right: 10px; }
.input-group .unit { color: #848e9c; margin-left: 8px; font-size: 11px; }
.slider-container { margin: 8px 0 15px; padding: 0 5px; }
input[type=range] { -webkit-appearance: none; width: 100%; height: 3px; background: #2b3139; border-radius: 2px; outline: none; }
input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 14px; height: 14px; background: var(--primary-color); border-radius: 50%; cursor: pointer; }
.trade-btn { width: 100%; padding: 10px; border-radius: 4px; font-weight: bold; font-size: 14px; border: none; cursor: pointer; margin-top: 5px; }
.right-col-tabs { display: flex; border-bottom: 1px solid #2b3139; flex-shrink: 0;}
.right-col-tab { flex: 1; text-align: center; padding: 10px 0; font-size: 13px; font-weight: 600; color: #848e9c; cursor: pointer; background: #161a1e; }
.right-col-tab.active { color: white; background: #1e2329;}
.search-box { padding: 10px 15px 15px; position: relative; }
.search-box i { position: absolute; left: 25px; top: 22px; color: #848e9c; font-size: 12px; }
.search-box input { width: 100%; background: #2b3139; border: 1px solid transparent; border-radius: 6px; padding: 8px 10px 8px 35px; color: white; font-size: 13px; outline: none; transition: 0.2s; }
.search-box input:focus { border-color: var(--primary-color); }
/* Futures specific styles */
.futures-config { display: flex; gap: 10px; margin-bottom: 10px; }
.config-btn { flex: 1; background: #2b3139; color: white; padding: 6px; border-radius: 4px; border: 1px solid transparent; cursor: pointer; font-size: 12px; text-align: center; font-weight: 600; }
.trade-actions { display: flex; gap: 12px; margin-top: 8px; }
.btn-long { background: #0ecb81; color: white; }
.btn-short { background: #f6465d; color: white; }
#pairs-list { height: 684px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #2b3139 transparent; }
#pairs-list::-webkit-scrollbar { width: 4px; }
#pairs-list::-webkit-scrollbar-thumb { background: #2b3139; border-radius: 10px; }
.pair-item { display: flex; align-items: center; padding: 10px 15px; cursor: pointer; border-bottom: 1px solid rgba(255,255,255,0.02); height: 38px; }
.pair-item.active { background: #1e2329; border-left: 3px solid var(--primary-color); }
.coin-icon { width: 22px; height: 22px; margin-right: 12px; border-radius: 50%; }
.futures-config { display: flex; gap: 10px; margin-bottom: 12px; }
.config-btn { flex: 1; background: #2b3139; color: white; padding: 8px; border-radius: 4px; text-align: center; font-size: 12px; cursor: pointer; border: 1px solid #3c424a; }
.input-group { background: #2b3139; border-radius: 4px; padding: 8px 12px; display: flex; align-items: center; margin-bottom: 10px; border: 1px solid #3c424a; }
.input-group:focus-within { border-color: var(--primary-color); }
.input-group input { background: transparent; border: none; color: white; flex: 1; outline: none; font-size: 14px; width: 100%; }
.input-group .label { color: #848e9c; font-size: 12px; margin-right: 10px; min-width: 40px; }
.input-group .unit { color: #848e9c; margin-left: 8px; font-size: 12px; }
.slider-container { margin: 15px 0 20px; }
input[type=range] { width: 100%; height: 4px; background: #2b3139; border-radius: 2px; outline: none; }
.trade-btn { width: 100%; padding: 12px; border-radius: 4px; font-weight: bold; font-size: 15px; border: none; cursor: pointer; color: white; transition: 0.2s; }
.btn-buy { background: #0ecb81; }
.btn-sell { background: #f6465d; }
.trade-btn:hover { filter: brightness(1.1); }
.right-col-tabs { display: flex; border-bottom: 1px solid #2b3139; flex-shrink: 0; }
.right-col-tab { flex: 1; text-align: center; padding: 12px 0; font-size: 13px; font-weight: 600; color: #848e9c; cursor: pointer; }
.right-col-tab.active { color: white; background: #1e2329; }
.ob-header { display: flex; justify-content: space-between; padding: 8px 15px; font-size: 11px; color: #848e9c; border-bottom: 1px solid #2b3139; }
.ob-row { display: flex; justify-content: space-between; padding: 3px 15px; font-size: 12px; position: relative; cursor: pointer; }
#ob-panel-content { flex: 1; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #2b3139 transparent; }
#ob-panel-content::-webkit-scrollbar { width: 4px; }
#ob-panel-content::-webkit-scrollbar-thumb { background: #2b3139; border-radius: 10px; }
</style>
<div class="trading-page-wrapper">
<?php // The real header is included in the file, this is a placeholder for the structure. ?>
<div class="trading-container flex-column flex-lg-row">
<div class="left-col d-none d-lg-flex flex-column">
<div class="trading-container">
<!-- Left Column -->
<div class="left-col d-none d-lg-flex">
<div class="category-tabs">
<div class="category-tab" onclick="location.href='options.php'"><?php echo __('nav_options'); ?></div>
<div class="category-tab" onclick="location.href='spot.php'"><?php echo __('nav_spot'); ?></div>
<div class="category-tab active" onclick="location.href='futures.php'"><?php echo __('nav_futures'); ?></div>
</div>
<div class="search-box">
<i class="fas fa-search"></i>
<input type="text" id="pair-search" placeholder="<?php echo __('search_contract'); ?>">
</div>
<div id="pairs-list" class="flex-grow-1" style="overflow-y: auto;"></div>
<div class="search-box"><i class="fas fa-search"></i><input type="text" id="pair-search" placeholder="<?php echo __('search_contract'); ?>"></div>
<div id="pairs-list"></div>
</div>
<!-- Center Column -->
<div class="center-col">
<div id="websocket-status"></div>
<div class="chart-header">
<div style="display: flex; align-items: center; gap: 10px; padding-right: 15px; border-right: 1px solid #2b3139;">
<img id="curr-icon" src="" class="coin-icon" style="margin:0; width:28px; height:28px;">
<span id="curr-pair" style="font-weight: 800; font-size: 16px;">--/--</span>
<div style="display: flex; align-items: center; gap: 10px;">
<img id="curr-icon" src="" class="coin-icon">
<span id="curr-pair" style="font-weight: 800; font-size: 18px; color:white;">--/--</span>
</div>
<div class="stats-item">
<div id="curr-price" style="font-size: 18px; font-weight: 800; color: #0ecb81; line-height: 1.2;">--</div>
<div id="curr-change" style="font-size: 11px; font-weight: 600;">--</div>
<div id="curr-price" style="font-size: 20px; font-weight: 800; color: #0ecb81;">--</div>
<div id="curr-change" style="font-size: 12px; font-weight: 600;">--</div>
</div>
<div class="stats-item"><span class="stats-label"><?php echo __('24h_high'); ?></span><span class="stats-value" id="h-high">--</span></div>
<div class="stats-item"><span class="stats-label"><?php echo __('24h_low'); ?></span><span class="stats-value" id="h-low">--</span></div>
<div class="stats-item"><span class="stats-label"><?php echo __('24h_vol'); ?></span><span class="stats-value" id="h-vol">--</span></div>
</div>
<div class="chart-box" id="tv_chart_container"></div>
<div class="bottom-content-wrapper">
<div class="order-box">
<div class="order-box">
<div class="futures-config">
<div class="config-btn" id="margin-mode"><?php echo __('cross'); ?></div>
<div class="config-btn"><?php echo __('cross'); ?></div>
<div class="config-btn" id="leverage-val">20x</div>
</div>
<div class="input-group"><span class="label"><?php echo __('price'); ?></span><input type="number" id="futures-price" placeholder="<?php echo __('market'); ?>" disabled><span class="unit">USDT</span></div>
<div class="input-group"><span class="label"><?php echo __('amount'); ?></span><input type="number" id="futures-amount" placeholder="0.00"><span class="unit coin-name">--</span></div>
<div class="slider-container"><input type="range" id="futures-slider" min="0" max="100" value="0" step="1"></div>
<div style="font-size: 11px; color: #848e9c; margin-bottom: 8px;"><span><?php echo __('available'); ?>: </span><span id="futures-avail" style="color:white; font-weight:600">0.00 USDT</span></div>
<div class="trade-actions">
<button id="btn-buy-long" class="trade-btn btn-long"><?php echo __('open_long'); ?></button>
<button id="btn-sell-short" class="trade-btn btn-short"><?php echo __('open_short'); ?></button>
<div class="input-group"><span class="label"><?php echo __('price'); ?></span><input type="text" placeholder="<?php echo __('market'); ?>" disabled><span class="unit">USDT</span></div>
<div class="input-group"><span class="label"><?php echo __('amount'); ?></span><input type="number" id="futures-amount" step="0.0001" placeholder="0.00"><span class="unit coin-name">--</span></div>
<div class="slider-container"><input type="range" id="futures-slider" min="0" max="100" value="0"></div>
<div style="font-size: 12px; color: #848e9c; margin-bottom: 12px;"><?php echo __('available'); ?>: <span id="avail-usdt" style="color:white; font-weight:600">0.00</span> USDT</div>
<div style="display: flex; gap: 15px;">
<button id="btn-buy" class="trade-btn btn-buy"><?php echo __('open_long'); ?></button>
<button id="btn-sell" class="trade-btn btn-sell"><?php echo __('open_short'); ?></button>
</div>
</div>
<div class="records-container">
@ -155,279 +134,168 @@ if ($user_id) {
<div class="record-tab active" data-status="positions"><?php echo __('current_positions'); ?></div>
<div class="record-tab" data-status="history"><?php echo __('history_orders'); ?></div>
</div>
<div id="records-list"></div>
<div id="records-list-container">
<table style="width:100%;" id="records-table"></table>
</div>
</div>
</div>
</div>
<div class="right-col d-none d-lg-flex">
<!-- Right Column -->
<div class="right-col d-none d-xl-flex">
<div class="right-col-tabs">
<div class="right-col-tab active" data-tab="ob"><?php echo __('order_book'); ?></div>
<div class="right-col-tab" data-tab="trades"><?php echo __('trades'); ?></div>
</div>
<div id="ob-panel" class="ob-panel">
<div class="ob-header">
<span><?php echo __('price'); ?>(USDT)</span>
<span><?php echo __('amount'); ?>(<span class="coin-name">--</span>)</span>
<span><?php echo __('total'); ?></span>
</div>
<div class="ob-content">
<div id="ob-panel" style="flex: 1; display: flex; flex-direction: column; overflow: hidden;">
<div class="ob-header"><span><?php echo __('price'); ?></span><span><?php echo __('amount'); ?></span></div>
<div id="ob-panel-content">
<div id="asks-list"></div>
<div id="mid-price">--</div>
<div id="mid-price" style="padding:10px 15px; text-align:center; font-weight:800; font-size:16px; border-top:1px solid #2b3139; border-bottom:1px solid #2b3139;">--</div>
<div id="bids-list"></div>
</div>
</div>
<div id="trades-panel" class="trades-panel" style="display: none;">
<div class="trades-header">
<span><?php echo __('price'); ?>(USDT)</span>
<span><?php echo __('amount'); ?>(<span class="coin-name">--</span>)</span>
<span><?php echo __('time'); ?></span>
</div>
<div id="trades-content"></div>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
<script>
const userAssets = <?php echo json_encode($user_assets); ?>;
document.addEventListener('DOMContentLoaded', function() {
// --- Globals ---
let currentPair = 'BTCUSDT';
let currentPrice = 0;
let ws;
const marketData = {};
const pairs = [
'BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'SOLUSDT', 'XRPUSDT', 'ADAUSDT', 'DOGEUSDT', 'AVAXUSDT',
'DOTUSDT', 'MATICUSDT', 'LTCUSDT', 'TRXUSDT', 'LINKUSDT', 'UNIUSDT', 'ATOMUSDT', 'ETCUSDT',
'BCHUSDT', 'FILUSDT', 'ICPUSDT', 'NEARUSDT', 'AAVEUSDT', 'ALGOUSDT'
];
// --- DOM Elements ---
let currentPair = 'BTCUSDT', currentPrice = 0, ws, leverage = 20;
const marketData = {}, pairs = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'SOLUSDT', 'XRPUSDT', 'ADAUSDT', 'DOGEUSDT', 'AVAXUSDT', 'DOTUSDT', 'MATICUSDT', 'LTCUSDT', 'TRXUSDT', 'LINKUSDT', 'UNIUSDT', 'ATOMUSDT', 'ETCUSDT', 'BCHUSDT', 'FILUSDT', 'ICPUSDT', 'NEARUSDT', 'AAVEUSDT', 'ALGOUSDT'];
const dom = {
websocketStatus: document.getElementById('websocket-status'),
currIcon: document.getElementById('curr-icon'),
currPair: document.getElementById('curr-pair'),
currPrice: document.getElementById('curr-price'),
currChange: document.getElementById('curr-change'),
hHigh: document.getElementById('h-high'),
hLow: document.getElementById('h-low'),
hVol: document.getElementById('h-vol'),
tvChartContainer: document.getElementById('tv_chart_container'),
tvContainer: document.getElementById('tv_chart_container'),
pairsList: document.getElementById('pairs-list'),
pairSearch: document.getElementById('pair-search'),
asksList: document.getElementById('asks-list'),
bidsList: document.getElementById('bids-list'),
midPrice: document.getElementById('mid-price'),
tradesContent: document.getElementById('trades-content'),
coinNameSpans: document.querySelectorAll('.coin-name'),
futuresPriceInput: document.getElementById('futures-price'),
availFuturesSpan: document.getElementById('futures-avail')
availUsdt: document.getElementById('avail-usdt'),
coinNames: document.querySelectorAll('.coin-name'),
amountInput: document.getElementById('futures-amount'),
slider: document.getElementById('futures-slider'),
recordsTable: document.getElementById('records-table')
};
// --- Balance Management ---
function updateAvailableBalances() {
const usdtBalance = parseFloat(userAssets.USDT || 0).toFixed(2);
if (dom.availFuturesSpan) {
dom.availFuturesSpan.innerText = `${usdtBalance} USDT`;
}
function updateBalances() {
dom.availUsdt.innerText = parseFloat(userAssets.USDT || 0).toFixed(2);
dom.coinNames.forEach(el => el.innerText = currentPair.replace('USDT', ''));
}
// --- TradingView Chart ---
function initChartWidget(symbol) {
if (dom.tvChartContainer.firstChild) {
dom.tvChartContainer.innerHTML = '';
}
const widget = new TradingView.widget({
"autosize": true,
"symbol": `BINANCE:${symbol}USDT.PERP`,
"interval": "15",
"timezone": "Etc/UTC",
"theme": "dark",
"style": "1",
"locale": "<?php echo $lang == 'zh' ? 'zh_CN' : 'en'; ?>",
"toolbar_bg": "#f1f3f6",
"enable_publishing": false,
"withdateranges": true,
"hide_side_toolbar": true,
"allow_symbol_change": false,
"container_id": "tv_chart_container",
"studies": [
"Volume@tv-basicstudies"
],
"overrides": {
"paneProperties.background": "#161a1e",
"paneProperties.vertGridProperties.color": "rgba(255, 255, 255, 0.05)",
"paneProperties.horzGridProperties.color": "rgba(255, 255, 255, 0.05)",
"scalesProperties.textColor" : "#848e9c",
}
function initChart(symbol) {
dom.tvContainer.innerHTML = '';
new TradingView.widget({
"autosize": true, "symbol": `BINANCE:${symbol}`, "interval": "15", "theme": "dark", "style": "1",
"locale": "<?php echo $lang == 'zh' ? 'zh_CN' : 'en'; ?>", "container_id": "tv_chart_container",
"hide_side_toolbar": true, "allow_symbol_change": false, "studies": ["Volume@tv-basicstudies"],
"overrides": { "paneProperties.background": "#161a1e" }
});
}
// --- WebSocket Connection ---
function connectWebSocket() {
function connectWS() {
if (ws) ws.close();
const streams = pairs.map(p => `${p.toLowerCase()}@ticker`).concat([`${currentPair.toLowerCase()}@depth20@100ms`, `${currentPair.toLowerCase()}@trade`]);
const streams = pairs.map(p => `${p.toLowerCase()}@ticker`).concat([`${currentPair.toLowerCase()}@depth20@100ms`]);
ws = new WebSocket(`wss://stream.binance.com:9443/stream?streams=${streams.join('/')}`);
ws.onmessage = (event) => {
const { stream, data } = JSON.parse(event.data);
ws.onmessage = (e) => {
const { stream, data } = JSON.parse(e.data);
if (!data) return;
const type = stream.split('@')[1];
if (type === 'ticker') {
if (stream.endsWith('@ticker')) {
marketData[data.s] = data;
if (data.s === currentPair) {
currentPrice = parseFloat(data.c);
updatePriceUI(data);
dom.currPrice.innerText = currentPrice.toLocaleString('en-US', {minimumFractionDigits:2});
dom.currChange.innerText = (data.P >= 0 ? '+' : '') + parseFloat(data.P).toFixed(2) + '%';
dom.currPrice.style.color = dom.currChange.style.color = data.P >= 0 ? '#0ecb81' : '#f6465d';
dom.midPrice.innerText = currentPrice.toFixed(2);
}
if (dom.pairsList.offsetParent) renderPairs();
} else if (type.startsWith('depth')) {
renderOrderBook(data.bids, data.asks);
} else if (type === 'trade') {
renderTrades(data);
} else if (stream.endsWith('@depth20@100ms')) {
dom.asksList.innerHTML = data.asks.slice(0, 20).reverse().map(a => `<div class="ob-row" onclick="document.getElementById('futures-amount').value=${parseFloat(a[1]).toFixed(4)};"><span style="color:#f6465d">${parseFloat(a[0]).toFixed(2)}</span><span>${parseFloat(a[1]).toFixed(4)}</span></div>`).join('');
dom.bidsList.innerHTML = data.bids.slice(0, 20).map(b => `<div class="ob-row" onclick="document.getElementById('futures-amount').value=${parseFloat(b[1]).toFixed(4)};"><span style="color:#0ecb81">${parseFloat(b[0]).toFixed(2)}</span><span>${parseFloat(b[1]).toFixed(4)}</span></div>`).join('');
}
};
ws.onerror = (error) => console.error('WebSocket Error:', error);
ws.onclose = () => setTimeout(connectWebSocket, 5000);
}
// --- UI Rendering ---
function updatePriceUI(d) {
if (!d) return;
const color = d.P >= 0 ? '#0ecb81' : '#f6465d';
const priceStr = parseFloat(d.c).toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2});
document.title = `${priceStr} | ${d.s.replace('USDT', '/USDT')}`;
dom.currPrice.innerText = priceStr;
dom.currPrice.style.color = color;
dom.currChange.innerText = `${d.P >= 0 ? '+' : ''}${parseFloat(d.P).toFixed(2)}%`;
dom.currChange.style.color = color;
dom.hHigh.innerText = parseFloat(d.h).toLocaleString('en-US', {minimumFractionDigits: 2});
dom.hLow.innerText = parseFloat(d.l).toLocaleString('en-US', {minimumFractionDigits: 2});
dom.hVol.innerText = `${(parseFloat(d.v) / 1000).toFixed(1)}K`;
dom.midPrice.innerText = priceStr;
dom.midPrice.style.color = color;
ws.onclose = () => setTimeout(connectWS, 5000);
}
function renderPairs() {
const query = dom.pairSearch.value.toUpperCase();
const filteredPairs = pairs.filter(p => p.includes(query));
dom.pairsList.innerHTML = filteredPairs.map(p => {
const q = document.getElementById('pair-search').value.toUpperCase();
dom.pairsList.innerHTML = pairs.filter(p => p.includes(q)).map(p => {
const d = marketData[p] || {};
const icon = `https://assets.coincap.io/assets/icons/${p.replace('USDT','').toLowerCase()}@2x.png`;
const price = d.c ? parseFloat(d.c).toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2}) : '--';
const change = d.P ? `${parseFloat(d.P) >= 0 ? '+' : ''}${parseFloat(d.P).toFixed(2)}%` : '--';
const color = (d.P && parseFloat(d.P) >= 0) ? '#0ecb81' : '#f6465d';
return `
<div class="pair-item ${p === currentPair ? 'active' : ''}" data-pair="${p}">
<img src="${icon}" class="coin-icon" onerror="this.style.display='none'">
<div style="flex:1"><div style="font-weight:600; font-size:13px">${p.replace('USDT', '')}<span style="color:#848e9c">/USDT</span></div></div>
<div style="text-align:right">
<div style="font-size:13px; font-weight:600">${price}</div>
<div style="font-size:11px; color:${color}">${change}</div>
</div>
</div>`;
return `<div class="pair-item ${p === currentPair ? 'active' : ''}" onclick="switchPair('${p}')">
<img src="https://assets.coincap.io/assets/icons/${p.replace('USDT','').toLowerCase()}@2x.png" class="coin-icon" onerror="this.style.opacity=0">
<div style="flex:1; color:white; font-weight:600">${p.replace('USDT','/USDT')}</div>
<div style="text-align:right"><div style="color:white; font-weight:600">${d.c ? parseFloat(d.c).toFixed(2) : '--'}</div><div style="color:${d.P>=0?'#0ecb81':'#f6465d'}; font-size:11px">${d.P?(d.P>=0?'+':'')+parseFloat(d.P).toFixed(2)+'%':'--'}</div></div>
</div>`;
}).join('');
}
function renderOrderBook(bids, asks) {
const renderList = (element, data, isAsks) => {
if (!element) return;
const maxRows = Math.floor(element.offsetHeight / 20);
if (maxRows <= 0) return;
window.switchPair = (p) => {
currentPair = p;
dom.currPair.innerText = p.replace('USDT', '/USDT') + ' Perp';
dom.currIcon.src = `https://assets.coincap.io/assets/icons/${p.replace('USDT','').toLowerCase()}@2x.png`;
initChart(p);
updateBalances();
connectWS();
};
const relevantData = isAsks ? data.slice(0, maxRows).reverse() : data.slice(0, maxRows);
const maxVol = Math.max(...relevantData.map(([, q]) => parseFloat(q))) || 1;
async function placeOrder(side) {
const amount = parseFloat(dom.amountInput.value);
if (!amount || amount <= 0) { alert("Amount > 0"); return; }
const price = currentPrice;
const total = amount * price;
element.innerHTML = relevantData.map(([price, quantity]) => {
const p = parseFloat(price), q = parseFloat(quantity);
const barWidth = (q / maxVol) * 100;
const color = isAsks ? '#f6465d' : '#0ecb81';
const bgColor = isAsks ? 'rgba(246, 70, 93, 0.2)' : 'rgba(14, 203, 129, 0.2)';
return `
<div class="ob-row" onclick="setPrice(${p})">
<div class="ob-bar" style="width:${barWidth}%; background-color:${bgColor};"></div>
<span style="color:${color}; z-index:1;">${p.toFixed(2)}</span>
<span style="z-index:1;">${q.toFixed(4)}</span>
<span style="z-index:1;">${(p * q).toFixed(2)}</span>
</div>`;
}).join('');
};
renderList(dom.asksList, asks, true);
renderList(dom.bidsList, bids, false);
try {
const r = await fetch('api/place_order.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
symbol: currentPair, type: 'futures', side: side, order_type: 'market',
price: price, amount: amount, total: total, leverage: leverage
})
});
const res = await r.json();
if (res.success) { alert("<?php echo __('order_placed'); ?>"); fetchOrders(); }
else { alert(res.error || "Error"); }
} catch(e) { alert("Network Error"); }
}
function renderTrades(trade) {
if (!dom.tradesContent) return;
const color = trade.m ? '#f6465d' : '#0ecb81';
const time = new Date(trade.T).toLocaleTimeString('en-GB');
const row = `
<div class="trade-row">
<span style="color:${color}">${parseFloat(trade.p).toFixed(2)}</span>
<span>${parseFloat(trade.q).toFixed(4)}</span>
<span>${time}</span>
</div>`;
dom.tradesContent.insertAdjacentHTML('afterbegin', row);
if (dom.tradesContent.children.length > 50) {
dom.tradesContent.removeChild(dom.tradesContent.lastChild);
}
document.getElementById('btn-buy').onclick = () => placeOrder('buy');
document.getElementById('btn-sell').onclick = () => placeOrder('sell');
let activeTab = 'positions';
async function fetchOrders() {
try {
const r = await fetch(`api/get_orders.php?type=futures&status=${activeTab}`);
const res = await r.json();
if (res.success) renderOrders(res.data);
} catch(e) {}
}
function switchPair(pair) {
currentPair = pair;
const coinName = pair.replace('USDT', '');
dom.currPair.innerText = `${coinName}/USDT Perpetual`;
dom.currIcon.src = `https://assets.coincap.io/assets/icons/${coinName.toLowerCase()}@2x.png`;
dom.coinNameSpans.forEach(el => el.innerText = coinName);
updatePriceUI(marketData[pair]);
initChartWidget(pair);
renderPairs();
dom.asksList.innerHTML = '';
dom.bidsList.innerHTML = '';
dom.tradesContent.innerHTML = '';
connectWebSocket();
}
window.setPrice = (price) => {
if(dom.futuresPriceInput) dom.futuresPriceInput.value = price;
}
// --- Event Listeners ---
dom.pairsList.addEventListener('click', (e) => {
const pairItem = e.target.closest('.pair-item');
if (pairItem && pairItem.dataset.pair) {
switchPair(pairItem.dataset.pair);
}
});
dom.pairSearch.addEventListener('input', renderPairs);
document.querySelectorAll('.right-col-tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelector('.right-col-tab.active').classList.remove('active');
tab.classList.add('active');
const isOb = tab.dataset.tab === 'ob';
document.getElementById('ob-panel').style.display = isOb ? 'flex' : 'none';
document.getElementById('trades-panel').style.display = isOb ? 'none' : 'flex';
function renderOrders(orders) {
if (!orders || orders.length === 0) { dom.recordsTable.innerHTML = '<tr><td colspan="6" style="text-align:center; padding:30px; color:#848e9c"><?php echo __('no_records'); ?></td></tr>'; return; }
let h = `<thead><tr><th><?php echo __('pair'); ?></th><th><?php echo __('direction'); ?></th><th><?php echo __('price'); ?></th><th><?php echo __('amount'); ?></th><th><?php echo __('leverage'); ?></th><th><?php echo __('time'); ?></th></tr></thead><tbody>`;
orders.forEach(o => {
h += `<tr><td>${o.symbol}</td><td style="color:${o.side==='buy'?'#0ecb81':'#f6465d'}">${o.side==='buy'?'<?php echo __('buy_long'); ?>':'<?php echo __('sell_short'); ?>'}</td><td>${parseFloat(o.price).toFixed(2)}</td><td>${parseFloat(o.amount).toFixed(4)}</td><td>${o.leverage}x</td><td style="color:#848e9c">${o.created_at}</td></tr>`;
});
dom.recordsTable.innerHTML = h + '</tbody>';
}
document.querySelectorAll('.record-tab').forEach(t => t.onclick = () => {
if (!t.dataset.status) return;
document.querySelector('.record-tabs .active').classList.remove('active');
t.classList.add('active');
activeTab = t.dataset.status;
fetchOrders();
});
// --- Initial Load ---
updateAvailableBalances();
switchPair('BTCUSDT');
fetchOrders();
setInterval(fetchOrders, 5000);
});
</script>
<script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
<?php include 'footer.php'; ?>

View File

@ -145,6 +145,10 @@ $translations = [
'order_date' => 'Order Date',
'options_order_executing' => 'Order is executing, settlement upon countdown completion.',
'options_order_settled' => 'Order settled.',
'win' => 'Win',
'loss' => 'Loss',
'profit_amount' => 'Profit Amount',
'loss_amount' => 'Loss Amount',
'kyc_status' => 'KYC Verification',
'kyc_none' => 'Unverified',
@ -170,6 +174,7 @@ $translations = [
'orders' => 'Orders',
'order_placed' => 'Order Placed',
'min_amount' => 'Min Amount',
'trades' => 'Trades',
'home_slide1_title' => 'NovaEx Global Launch',
'home_slide1_desc' => 'Experience the next generation of digital asset trading with ultra-low latency and bank-grade security.',
@ -359,6 +364,10 @@ $translations = [
'order_date' => '下单日期',
'options_order_executing' => '订单执行中,倒计时结束后结算。',
'options_order_settled' => '订单已结算。',
'win' => '盈利',
'loss' => '亏损',
'profit_amount' => '盈利金额',
'loss_amount' => '亏损金额',
'kyc_status' => '实名认证',
'kyc_none' => '未认证',
@ -384,15 +393,16 @@ $translations = [
'orders' => '订单',
'order_placed' => '下单成功',
'min_amount' => '最小金额',
'trades' => '最新成交',
'home_slide1_title' => 'NovaEx 全球发布',
'home_slide1_desc' => '体验超低延迟和银行级安全的新一代数字资产交易。',
'home_slide2_title' => '100倍杠杆合约交易',
'home_slide2_desc' => '通过我们的专业永续合约最大限度地提高您的资金效率。',
'home_slide3_title' => '安全数字资产质押',
'home_slide3_desc' => 'Earn passive income on your idle assets with our high-yield staking pools.',
'home_slide3_desc' => '通过我们的高收益质押池为您的闲置资产赚取被动收入。',
'home_download_title' => '随时随地,随心交易',
'home_download_desc' => 'Stay connected to the markets with the NovaEx mobile app. Experience professional trading features in the palm of your hand.',
'home_download_desc' => '通过 NovaEx 移动应用随时随地关注市场。在掌中体验专业交易功能。',
'fast_secure' => '快速且安全',
'fast_secure_desc' => '为您的所有数据提供军用级加密。',
'real_time' => '实时行情',
@ -454,4 +464,4 @@ $lang = $_SESSION['lang'];
function __($key, $default = '') {
global $translations, $lang;
return $translations[$lang][$key] ?? ($translations['en'][$key] ?? ($default ?: $key));
}
}

View File

@ -1,200 +1,258 @@
<?php
if (session_status() === PHP_SESSION_NONE) { session_start(); }
if (!isset($_SESSION['user_id'])) { header('Location: login.php'); exit; }
include 'header.php';
require_once 'db/config.php';
require_once 'includes/i18n.php';
$user_id = $_SESSION['user_id'] ?? null;
if (!$user_id) { header('Location: login.php'); exit; }
$user_id = $_SESSION['user_id'];
$user_assets = [];
if ($user_id) {
$pdo = db();
$stmt = $pdo->prepare("SELECT currency, balance FROM assets WHERE user_id = ?");
// $db is already defined in header.php
$stmt = $db->prepare("SELECT balance FROM users WHERE id = ?");
$stmt->execute([$user_id]);
$usdt_balance = $stmt->fetchColumn();
$user_assets['USDT'] = (float)($usdt_balance ?: 0);
$stmt = $db->prepare("SELECT symbol, amount FROM user_assets WHERE user_id = ?");
$stmt->execute([$user_id]);
$assets_data = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
if ($assets_data) {
$user_assets = $assets_data;
foreach ($assets_data as $symbol => $amount) {
$user_assets[$symbol] = (float)$amount;
}
}
}
?>
<style>
/* UNIFIED 3-column layout CSS */
html, body {
/* UNLOCKED for scrolling */
}
.trading-page-wrapper {
display: flex;
flex-direction: column;
min-height: 100vh; /* Ensure it takes at least full height */
background: #0b0e11;
}
.trading-page-wrapper .navbar { flex-shrink: 0; }
.trading-container { flex-grow: 1; display: flex; min-height: 800px; }
.trading-page-wrapper { display: flex; flex-direction: column; min-height: 100vh; background: #0b0e11; }
.trading-container { flex-grow: 1; display: flex; min-height: 800px; max-width: 1920px; margin: 0 auto; width: 100%; }
.left-col { width: 280px; flex-shrink: 0; background: #161a1e; display: flex; flex-direction: column; border-right: 1px solid #2b3139; }
.center-col { flex: 1; min-width: 600px; display: flex; flex-direction: column; border-right: 1px solid #2b3139; }
.right-col { width: 300px; flex-shrink: 0; background: #161a1e; display: flex; flex-direction: column; }
.left-col { width: 260px; flex-shrink: 0; background: #161a1e; display: flex; flex-direction: column; border-right: 1px solid #2b3139; }
.center-col { flex: 1; min-width: 600px; display: flex; flex-direction: column; }
.right-col { width: 260px; flex-shrink: 0; background: #161a1e; display: flex; flex-direction: column; border-left: 1px solid #2b3139; }
.chart-header { flex-shrink: 0; height: 55px; padding: 0 20px; display: flex; align-items: center; background: #161a1e; border-bottom: 1px solid #2b3139; gap: 25px; }
.chart-box { height: 450px; flex-shrink: 0; background: #161a1e; border-bottom: 1px solid #2b3139; }
.bottom-content-wrapper { flex-grow: 1; background: #161a1e; display: flex; flex-direction: column; }
/* Center Column Content */
.chart-header { flex-shrink: 0; height: 50px; padding: 8px 20px; display: flex; align-items: center; background: #161a1e; border-bottom: 1px solid #2b3139; gap: 20px; }
.chart-box { height: 380px; flex-shrink: 0; border-bottom: 1px solid #2b3139; }
.bottom-content-wrapper { flex-grow: 1; background: #161a1e; overflow-y: auto; display: flex; flex-direction: column; }
.col-header { padding: 15px; font-weight: 600; border-bottom: 1px solid #2b3139; font-size: 14px; color: #eaecef; height: 50px; display: flex; align-items: center; }
#pairs-list { height: 756px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #2b3139 transparent; }
#pairs-list::-webkit-scrollbar { width: 4px; }
#pairs-list::-webkit-scrollbar-track { background: transparent; }
#pairs-list::-webkit-scrollbar-thumb { background: #2b3139; border-radius: 10px; }
/* Left & Right Column Content */
.col-header { flex-shrink: 0; padding: 15px; font-weight: 600; border-bottom: 1px solid #2b3139; font-size: 14px; }
#pairs-list, #order-book-container { flex: 1; overflow-y: auto; }
.coin-icon { width: 22px; height: 22px; margin-right: 12px; border-radius: 50%; }
.stats-item { display: flex; flex-direction: column; justify-content: center; }
.stats-label { font-size: 10px; color: #848e9c; }
.stats-value { font-size: 12px; font-weight: 600; color: white; }
.category-tabs { display: flex; padding: 15px 15px 5px; gap: 8px; }
.category-tab { flex: 1; text-align: center; padding: 6px 0; background: #2b3139; border-radius: 4px; font-size: 12px; color: #848e9c; cursor: pointer; border: 1px solid transparent; }
.category-tab.active { background: rgba(0, 82, 255, 0.1); border-color: var(--primary-color); color: var(--primary-color); font-weight: bold; }
.search-box { padding: 5px 15px 10px; position: relative; }
.search-box i { position: absolute; left: 25px; top: 50%; transform: translateY(-50%); color: #848e9c; font-size: 12px; }
.search-box input { width: 100%; background: #2b3139; border: 1px solid transparent; border-radius: 4px; padding: 8px 10px 8px 30px; color: white; font-size: 13px; outline: none; }
.pair-item { display: flex; align-items: center; padding: 10px 15px; cursor: pointer; border-bottom: 1px solid rgba(255,255,255,0.02); }
.pair-item.active { background: #1e2329; }
.coin-icon { width: 24px; height: 24px; margin-right: 10px; border-radius: 50%; }
.stats-item { display: flex; flex-direction: column; }
.stats-label { font-size: 11px; color: #848e9c; }
.stats-value { font-size: 13px; font-weight: 600; color: white; }
.category-tabs { display: flex; padding: 15px 15px 10px; gap: 8px; }
.category-tab { flex: 1; text-align: center; padding: 8px 0; background: #2b3139; border-radius: 6px; font-size: 12px; color: #848e9c; cursor: pointer; transition: 0.2s; border: 1px solid transparent; }
.category-tab.active { background: rgba(0, 102, 255, 0.1); color: var(--primary-color); border-color: var(--primary-color); font-weight: bold; }
.search-box { padding: 0 15px 15px; position: relative; }
.search-box i { position: absolute; left: 25px; top: 12px; color: #848e9c; font-size: 12px; }
.search-box input { width: 100%; background: #2b3139; border: 1px solid transparent; border-radius: 6px; padding: 8px 10px 8px 35px; color: white; font-size: 13px; outline: none; transition: 0.2s; }
.search-box input:focus { border-color: var(--primary-color); }
.pair-item { display: flex; align-items: center; padding: 12px 15px; cursor: pointer; border-bottom: 1px solid rgba(255,255,255,0.03); height: 42px; }
.pair-item:hover { background: #1e2329; }
.pair-item.active { background: #1e2329; border-left: 3px solid var(--primary-color); }
/* Order Panel - New Layout */
.order-panel { padding: 20px; background: #161a1e; border-bottom: 1px solid #2b3139; }
.panel-row { margin-bottom: 15px; }
.panel-label { color: #848e9c; font-size: 13px; margin-bottom: 8px; font-weight: 500; display: block; }
.duration-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; }
.time-btn { background: #2b3139; border: 1px solid #3c424a; color: #eaecef; padding: 8px 4px; border-radius: 6px; font-size: 13px; cursor: pointer; text-align: center; font-weight: 600; transition: all 0.2s; }
.time-btn.active { background: var(--primary-color); border-color: var(--primary-color); color: white; transform: translateY(-1px); }
.time-btn .rate { font-size: 10px; font-weight: 400; color: #0ecb81; margin-top: 1px; }
.time-btn.active .rate { color: #fff; opacity: 0.9; }
.input-wrapper { position: relative; }
.amount-input-group { background: #2b3139; border-radius: 6px; padding: 10px 12px; display: flex; align-items: center; border: 1px solid #3c424a; transition: 0.2s; }
.amount-input-group:focus-within { border-color: var(--primary-color); }
.amount-input-group input { background: transparent; border: none; color: white; flex: 1; outline: none; font-size: 16px; font-weight: 600; width: 100%; }
.amount-input-group .unit { color: #848e9c; font-weight: 600; font-size: 14px; }
.balance-info-row { display: flex; justify-content: space-between; margin-top: 8px; font-size: 12px; }
.balance-info-row .label { color: #848e9c; }
.balance-info-row .value { color: #eaecef; font-weight: 600; }
.balance-info-row .profit-val { color: #0ecb81; font-weight: 700; }
.action-buttons { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-top: 15px; }
.trade-btn { padding: 12px; border-radius: 8px; font-weight: 800; font-size: 15px; border: none; cursor: pointer; transition: 0.2s; display: flex; align-items: center; justify-content: center; gap: 8px; color: white; }
.btn-up { background: #0ecb81; }
.btn-down { background: #f6465d; }
.trade-btn:hover { filter: brightness(1.1); }
/* Records */
.records-container { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
.record-tabs { display: flex; padding: 0 20px; border-bottom: 1px solid #2b3139; flex-shrink: 0; }
.record-tab { padding: 12px 0; margin-right: 25px; font-size: 13px; font-weight: 600; color: #848e9c; cursor: pointer; position: relative; }
.record-tab.active { color: white; }
.record-tab.active::after { content: ''; position: absolute; bottom: 0; left: 0; width: 100%; height: 2px; background: var(--primary-color); }
.table-responsive { height: 350px; overflow-y: auto; padding: 0; scrollbar-width: thin; scrollbar-color: #2b3139 transparent; }
.table-responsive::-webkit-scrollbar { width: 4px; }
.table-responsive::-webkit-scrollbar-thumb { background: #2b3139; border-radius: 10px; }
table { width: 100%; font-size: 12px; border-collapse: collapse; }
th { color: #848e9c; font-weight: 500; text-align: left; padding: 10px 15px; border-bottom: 1px solid #2b3139; background: #161a1e; position: sticky; top: 0; z-index: 10; }
td { padding: 10px 15px; border-bottom: 1px solid rgba(255,255,255,0.02); color: #eaecef; height: 38px; }
/* Order Book */
.order-book-header { display: flex; justify-content: space-between; align-items: center; padding: 0 15px; height: 35px; border-bottom: 1px solid #2b3139; }
.order-book-header span { color: #848e9c; font-size: 11px; }
.book-row { display: flex; justify-content: space-between; font-size: 12px; padding: 3px 15px; position: relative; }
.book-row > div { width: 33.33%; text-align: right; z-index: 2; }
.book-row > div:first-child { text-align: left; }
.book-row .price.up { color: #0ecb81; }
.book-row .price.down { color: #f6465d; }
.depth-bar { position: absolute; top: 0; bottom: 0; z-index: 1; opacity: 0.15; }
.depth-bar.ask { right: 0; background: #f6465d; }
.depth-bar.bid { right: 0; background: #0ecb81; }
.ob-header { display: flex; justify-content: space-between; padding: 8px 15px; background: #161a1e; font-size: 11px; color: #848e9c; border-bottom: 1px solid #2b3139; }
.ob-row { display: flex; justify-content: space-between; padding: 4px 15px; font-size: 12px; }
.ob-row .price.up { color: #0ecb81; }
.ob-row .price.down { color: #f6465d; }
#order-book-list { flex: 1; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #2b3139 transparent; }
#order-book-list::-webkit-scrollbar { width: 4px; }
#order-book-list::-webkit-scrollbar-thumb { background: #2b3139; border-radius: 10px; }
/* Options-Specific Panel */
.order-panel { padding: 20px 25px; background: #1e2329; border-bottom: 1px solid #2b3139; }
.panel-label { color: #848e9c; font-size: 12px; margin-bottom: 10px; font-weight: 500; }
.duration-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; }
.time-btn { background: #2b3139; border: 1px solid #2b3139; color: #eaecef; padding: 8px 5px; border-radius: 8px; font-size: 13px; cursor: pointer; text-align: center; font-weight: 600; transition: all 0.2s ease-in-out; }
.time-btn:hover { background: #3c424a; border-color: #4a5057; }
.time-btn.active { background: linear-gradient(45deg, rgba(0, 82, 255, 0.2), rgba(0, 122, 255, 0.2)); border-color: var(--primary-color); color: #fff; transform: translateY(-2px); box-shadow: 0 4px 15px rgba(0, 102, 255, 0.2); }
.time-btn .rate { font-size: 10px; font-weight: normal; color: #0ecb81; margin-top: 3px; }
.input-group { background: #2b3139; border-radius: 8px; padding: 8px 15px; display: flex; align-items: center; border: 1px solid #2b3139; transition: all 0.2s ease-in-out; }
.input-group:focus-within { border-color: var(--primary-color); box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.15); }
.input-group input { background: transparent; border: none; color: white; flex: 1; outline: none; font-size: 16px; font-weight: 600; width: 100%; }
.input-group .unit { color: #848e9c; margin-left: 10px; font-weight: 500; font-size: 14px; }
.balance-info { display: flex; justify-content: space-between; font-size: 12px; color: #848e9c; margin-top: 8px; padding: 0 5px; }
.balance-info .label {color: #848e9c;}
.balance-info .value {color: white; font-weight: 600;}
.trade-btn { width: 100%; padding: 15px; border-radius: 8px; font-weight: bold; font-size: 16px; border: none; cursor: pointer; transition: all 0.2s ease-in-out; display: flex; align-items: center; justify-content: center; gap: 10px; }
.trade-btn:hover { transform: translateY(-2px); filter: brightness(1.1); }
.btn-up { background: linear-gradient(45deg, #0b9e6f, #14d291); box-shadow: 0 4px 20px rgba(14, 203, 129, 0.25); color: white; }
.btn-down { background: linear-gradient(45deg, #e04156, #f65368); box-shadow: 0 4px 20px rgba(246, 70, 93, 0.25); color: white; }
/* Countdown Modal Styles */
.countdown-modal {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.85); display: none; align-items: center; justify-content: center; z-index: 10000;
backdrop-filter: blur(4px);
}
.modal-content-card {
background: #161a1e; width: 360px; border-radius: 20px; padding: 30px; position: relative; color: white;
box-shadow: 0 20px 50px rgba(0,0,0,0.5); border: 1px solid #2b3139;
}
.modal-close {
position: absolute; top: 15px; right: 15px; width: 32px; height: 32px;
background: #2b3139; border-radius: 50%; display: flex; align-items: center; justify-content: center;
cursor: pointer; color: #848e9c; transition: 0.2s;
}
.modal-close:hover { background: #3c424a; color: white; }
.countdown-circle-wrap { display: flex; flex-direction: column; align-items: center; margin: 25px 0; }
.circle-progress { position: relative; width: 140px; height: 140px; }
.circle-progress svg { transform: rotate(-90deg); width: 140px; height: 140px; }
.circle-bg { fill: none; stroke: #2b3139; stroke-width: 10; }
.circle-bar {
fill: none; stroke: #0ecb81; stroke-width: 10; stroke-linecap: round;
stroke-dasharray: 408; stroke-dashoffset: 408; transition: stroke-dashoffset 0.1s linear;
}
.countdown-text { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 32px; font-weight: 800; }
.modal-info-row { display: flex; justify-content: space-between; margin-bottom: 12px; font-size: 14px; }
.modal-info-label { color: #848e9c; }
.modal-info-value { font-weight: 600; color: #eaecef; }
.modal-info-value.up { color: #0ecb81; }
.modal-info-value.down { color: #f6465d; }
.modal-footer-text { text-align: center; color: #848e9c; font-size: 12px; margin-top: 20px; border-top: 1px solid #2b3139; padding-top: 15px; }
/* Records (Order History) */
.records-container { flex-grow: 1; display: flex; flex-direction: column; min-height: 400px; }
.record-tabs { display: flex; padding: 0 20px; flex-shrink: 0; border-bottom: 1px solid #2b3139; }
.record-tab { padding: 12px 0; margin-right: 25px; font-size: 14px; font-weight: 600; color: #848e9c; cursor: pointer; border-bottom: 2px solid transparent; }
.record-tab.active { color: white; border-bottom-color: var(--primary-color); }
.records-table-container { flex-grow: 1; overflow-y: auto; padding: 0 20px; }
#records-list { width: 100%; font-size: 12px; }
#records-list th, #records-list td { padding: 10px 8px; border-bottom: 1px solid #2b3139; text-align: left; }
#records-list th { color: #848e9c; font-weight: 500; }
.status-win { color: #0ecb81; font-weight: 600; }
.status-loss { color: #f6465d; font-weight: 600; }
.status-pending { color: #ffc107; font-weight: 600; }
/* Countdown Modal */
.option-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(10, 20, 30, 0.4); z-index: 3000; display: none; align-items: center; justify-content: center; backdrop-filter: blur(10px); }
.option-modal-content { background: #1e2329; width: 420px; border-radius: 20px; border: 1px solid #2b3139; text-align: center; animation: modal-fade-in 0.3s; }
@keyframes modal-fade-in { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } }
.modal-header { padding: 20px; border-bottom: 1px solid #2b3139; font-size: 18px; font-weight: 600; color: white; }
.modal-body { padding: 25px 30px; }
.circular-timer { position: relative; width: 140px; height: 140px; margin: 0 auto 25px auto; }
.circular-timer-svg { transform: rotate(-90deg); }
.circular-timer-path { stroke-width: 10; fill: none; }
.timer-path-bg { stroke: #2b3139; }
.timer-path-progress { stroke: var(--primary-color); stroke-linecap: round; transition: stroke-dashoffset 1s linear; }
.timer-text { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
.timer-countdown { font-size: 36px; font-weight: 700; color: white; }
.timer-label { font-size: 12px; color: #848e9c; }
.modal-info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px 25px; text-align: left; }
.info-item .label { font-size: 12px; color: #848e9c; margin-bottom: 5px; }
.info-item .value { font-size: 15px; font-weight: 600; color: white; }
.modal-footer { padding: 20px; background: #161a1e; border-radius: 0 0 20px 20px; font-size: 13px; color: #848e9c; border-top: 1px solid #2b3139; }
#result-overlay {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
background: #161a1e; border-radius: 20px; display: none; flex-direction: column; align-items: center; justify-content: center; z-index: 10;
}
.result-icon { font-size: 60px; margin-bottom: 20px; }
.result-title { font-size: 24px; font-weight: 800; margin-bottom: 10px; }
.result-amount { font-size: 32px; font-weight: 800; margin-bottom: 30px; }
.result-btn { background: var(--primary-color); color: white; padding: 12px 40px; border-radius: 10px; font-weight: 700; cursor: pointer; border: none; }
</style>
<div class="trading-page-wrapper">
<?php // Header is included via main layout ?>
<div class="trading-container">
<!-- Left Column: Pair Selection -->
<div class="left-col d-none d-lg-flex flex-column">
<!-- Left: Pair Selection -->
<div class="left-col d-none d-lg-flex">
<div class="category-tabs">
<div class="category-tab active" onclick="location.href='options.php'"><?php echo __('nav_options'); ?></div>
<div class="category-tab" onclick="location.href='spot.php'"><?php echo __('nav_spot'); ?></div>
<div class="category-tab" onclick="location.href='futures.php'"><?php echo __('nav_futures'); ?></div>
</div>
<div class="search-box"><i class="fas fa-search"></i><input type="text" id="pair-search" placeholder="<?php echo __('search_currency'); ?>"></div>
<div id="pairs-list" class="flex-grow-1"></div>
<div class="search-box">
<i class="fas fa-search"></i>
<input type="text" id="pair-search" placeholder="<?php echo __('search_currency'); ?>">
</div>
<div id="pairs-list"></div>
</div>
<!-- Center Column: Chart & Order Panel -->
<!-- Center: Chart & Order -->
<div class="center-col">
<div class="chart-header">
<div style="display: flex; align-items: center; gap: 10px;"><img id="curr-icon" src="" class="coin-icon"><span id="curr-pair" style="font-weight: 800; font-size: 16px;">--/--</span></div>
<div class="stats-item"><div id="curr-price" style="font-size: 18px; font-weight: 800; color: #0ecb81; line-height: 1.2;">--</div><div id="curr-change" style="font-size: 11px; font-weight: 600;">--</div></div>
<div class="stats-item"><span class="stats-label"><?php echo __('24h_high'); ?></span><span class="stats-value" id="h-high">--</span></div>
<div class="stats-item"><span class="stats-label"><?php echo __('24h_low'); ?></span><span class="stats-value" id="h-low">--</span></div>
<div class="d-flex align-items-center" style="gap:12px;">
<img id="curr-icon" src="" class="coin-icon">
<span id="curr-pair" style="font-weight: 800; font-size: 18px; color:white;">--/--</span>
</div>
<div class="stats-item">
<div id="curr-price" style="font-size: 20px; font-weight: 800; color: #0ecb81; line-height: 1;">--</div>
<div id="curr-change" style="font-size: 12px; font-weight: 600; margin-top:2px;">--</div>
</div>
<div class="stats-item d-none d-md-flex"><span class="stats-label"><?php echo __('24h_high'); ?></span><span class="stats-value" id="h-high">--</span></div>
<div class="stats-item d-none d-md-flex"><span class="stats-label"><?php echo __('24h_low'); ?></span><span class="stats-value" id="h-low">--</span></div>
</div>
<div class="chart-box" id="tv_chart_container"></div>
<div class="bottom-content-wrapper">
<div class="order-panel">
<div style="display: grid; grid-template-columns: 2.5fr 1.5fr 2fr; gap: 20px; align-items: flex-end;">
<div>
<div class="panel-label"><?php echo __('settlement_time'); ?> / <?php echo __('profit'); ?></div>
<div class="duration-grid">
<div class="time-btn active" data-duration="60" data-rate="8" data-min="100">60s<div class="rate">+8%</div></div>
<div class="time-btn" data-duration="90" data-rate="12" data-min="5000">90s<div class="rate">+12%</div></div>
<div class="time-btn" data-duration="120" data-rate="15" data-min="30000">120s<div class="rate">+15%</div></div>
<div class="time-btn" data-duration="180" data-rate="20" data-min="100000">180s<div class="rate">+20%</div></div>
<div class="time-btn" data-duration="300" data-rate="32" data-min="300000">300s<div class="rate">+32%</div></div>
</div>
</div>
<div>
<div class="panel-label"><?php echo __('buy_amount'); ?></div>
<div class="input-group"><input type="number" id="option-amount" placeholder="<?php echo __('min_amount_is_pl'); ?> 100"><span class="unit">USDT</span></div>
<div class="balance-info">
<div><span class="label"><?php echo __('available'); ?>: </span><b id="usdt-balance" class="value">0.00</b></div>
<div><span class="label"><?php echo __('profit'); ?>: </span><b id="potential-profit" class="value" style="color: #0ecb81;">+0.00</b></div>
</div>
</div>
<div style="display:flex; gap:15px;">
<button class="trade-btn btn-up" id="btn-buy-up"><i class="fas fa-arrow-up fa-lg"></i> <?php echo __('buy_up'); ?></button>
<button class="trade-btn btn-down" id="btn-buy-down"><i class="fas fa-arrow-down fa-lg"></i> <?php echo __('buy_down'); ?></button>
<!-- Row 1: Duration -->
<div class="panel-row">
<label class="panel-label"><?php echo __('settlement_time'); ?> / <?php echo __('profit'); ?></label>
<div class="duration-grid">
<div class="time-btn active" data-duration="60" data-rate="8" data-min="100">60s<div class="rate">+8%</div></div>
<div class="time-btn" data-duration="90" data-rate="12" data-min="5000">90s<div class="rate">+12%</div></div>
<div class="time-btn" data-duration="120" data-rate="15" data-min="30000">120s<div class="rate">+15%</div></div>
<div class="time-btn" data-duration="180" data-rate="20" data-min="100000">180s<div class="rate">+20%</div></div>
<div class="time-btn" data-duration="300" data-rate="32" data-min="300000">300s<div class="rate">+32%</div></div>
</div>
</div>
<!-- Row 2: Amount -->
<div class="panel-row" style="margin-bottom: 10px;">
<label class="panel-label"><?php echo __('buy_amount'); ?></label>
<div class="amount-input-group">
<input type="number" id="option-amount" value="100" placeholder="<?php echo __('min_amount'); ?> 100">
<span class="unit">USDT</span>
</div>
</div>
<!-- Row 2.5: Balance & Profit -->
<div class="balance-info-row">
<div>
<span class="label"><?php echo __('available'); ?>: </span>
<span class="value"><span id="usdt-balance">0.00</span> USDT</span>
</div>
<div>
<span class="label"><?php echo __('expected_profit'); ?>: </span>
<span class="profit-val" id="potential-profit">+0.00</span>
</div>
</div>
<!-- Row 3: Buttons -->
<div class="action-buttons">
<button class="trade-btn btn-up" id="btn-buy-up">
<i class="fas fa-arrow-up"></i> <?php echo __('buy_up'); ?>
</button>
<button class="trade-btn btn-down" id="btn-buy-down">
<i class="fas fa-arrow-down"></i> <?php echo __('buy_down'); ?>
</button>
</div>
</div>
<div class="records-container">
<div class="record-tabs">
<div class="record-tab active" data-status="pending"><?php echo __('in_progress'); ?></div>
<div class="record-tab" data-status="completed"><?php echo __('settled'); ?></div>
</div>
<div class="records-table-container">
<div class="table-responsive">
<table id="records-list"></table>
</div>
</div>
</div>
</div>
<!-- Right Column: Order Book -->
<div class="right-col d-none d-lg-flex flex-column">
<!-- Right: Order Book -->
<div class="right-col d-none d-xl-flex">
<div class="col-header"><?php echo __('order_book'); ?></div>
<div class="order-book-header">
<span><?php echo __('price'); ?> (USDT)</span>
<span id="ob-amount-label"><?php echo __('amount'); ?> (BTC)</span>
<span><?php echo __('total'); ?></span>
<div class="ob-header">
<span><?php echo __('price'); ?>(USDT)</span>
<span><?php echo __('amount'); ?></span>
</div>
<div id="order-book-container">
<div id="order-book-list">
<div id="asks-list"></div>
<div id="current-price-display" style="padding: 8px 15px; text-align: center; font-weight: bold; font-size: 16px; border-top: 1px solid #2b3139; border-bottom: 1px solid #2b3139;"></div>
<div id="price-mid" style="padding:10px 15px; font-weight:800; text-align:center; font-size:16px; border-top:1px solid #2b3139; border-bottom:1px solid #2b3139;">--</div>
<div id="bids-list"></div>
</div>
</div>
@ -202,30 +260,56 @@ if ($user_id) {
</div>
<!-- Countdown Modal -->
<div class="option-modal-overlay" id="option-modal">
<div class="option-modal-content">
<div class="modal-header" id="modal-header"></div>
<div class="modal-body">
<div class="circular-timer">
<svg class="circular-timer-svg" viewBox="0 0 36 36">
<path class="circular-timer-path timer-path-bg" d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" />
<path class="circular-timer-path timer-path-progress" id="timer-progress" d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" />
<div id="countdown-modal" class="countdown-modal">
<div class="modal-content-card">
<div class="modal-close" onclick="closeCountdownModal()"><i class="fas fa-times"></i></div>
<div style="font-size: 20px; font-weight: 800; text-align: left;" id="modal-symbol">--</div>
<div class="countdown-circle-wrap">
<div class="circle-progress">
<svg>
<circle class="circle-bg" cx="70" cy="70" r="65"></circle>
<circle id="circle-bar" class="circle-bar" cx="70" cy="70" r="65"></circle>
</svg>
<div class="timer-text">
<div class="timer-countdown" id="timer-countdown">--</div>
<div class="timer-label"><?php echo __('seconds_remaining'); ?></div>
</div>
</div>
<div class="modal-info-grid">
<div class="info-item"><div class="label"><?php echo __('order_price'); ?></div><div class="value" id="modal-open-price"></div></div>
<div class="info-item"><div class="label"><?php echo __('buy_amount'); ?></div><div class="value" id="modal-amount"></div></div>
<div class="info-item"><div class="label"><?php echo __('current_price'); ?></div><div class="value" id="modal-current-price"></div></div>
<div class="info-item"><div class="label"><?php echo __('profit_and_loss'); ?></div><div class="value" id="modal-pnl"></div></div>
<div class="info-item"><div class="label"><?php echo __('order_time'); ?></div><div class="value" id="modal-start-time"></div></div>
<div class="info-item"><div class="label"><?php echo __('settlement_time'); ?></div><div class="value" id="modal-end-time"></div></div>
<div class="countdown-text" id="countdown-timer">00:00</div>
</div>
</div>
<div id="modal-footer" class="modal-footer"></div>
<div class="modal-info-row">
<span class="modal-info-label"><?php echo __('current_price'); ?></span>
<span class="modal-info-value" id="modal-curr-price">--</span>
</div>
<div class="modal-info-row">
<span class="modal-info-label"><?php echo __('period'); ?></span>
<span class="modal-info-value" id="modal-period">--</span>
</div>
<div class="modal-info-row">
<span class="modal-info-label"><?php echo __('direction'); ?></span>
<span class="modal-info-value" id="modal-direction">--</span>
</div>
<div class="modal-info-row">
<span class="modal-info-label"><?php echo __('amount'); ?></span>
<span class="modal-info-value" id="modal-amount">--</span>
</div>
<div class="modal-info-row">
<span class="modal-info-label"><?php echo __('price'); ?></span>
<span class="modal-info-value" id="modal-price">--</span>
</div>
<div class="modal-info-row">
<span class="modal-info-label"><?php echo __('expected_profit'); ?></span>
<span class="modal-info-value up" id="modal-profit">--</span>
</div>
<div class="modal-footer-text">
<?php echo __('final_price_notice'); ?>
</div>
<div id="result-overlay">
<div class="result-icon" id="result-icon"></div>
<div class="result-title" id="result-title"></div>
<div class="result-amount" id="result-amount"></div>
<button class="result-btn" onclick="closeCountdownModal()"><?php echo __('confirm'); ?></button>
</div>
</div>
</div>
@ -234,17 +318,13 @@ if ($user_id) {
const userAssets = <?php echo json_encode($user_assets); ?>;
document.addEventListener('DOMContentLoaded', function() {
// --- Globals ---
let currentPair = 'BTCUSDT', currentPrice = 0, ws, chartWidget;
let selectedDuration = 60, selectedRate = 8, minAmount = 100;
const marketData = {};
const pairs = [
'BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'SOLUSDT', 'XRPUSDT', 'ADAUSDT', 'DOGEUSDT', 'AVAXUSDT',
'DOTUSDT', 'MATICUSDT', 'LTCUSDT', 'TRXUSDT', 'LINKUSDT', 'UNIUSDT', 'ATOMUSDT', 'ETCUSDT',
'BCHUSDT', 'FILUSDT', 'ICPUSDT', 'NEARUSDT', 'AAVEUSDT', 'ALGOUSDT'
];
let countdownInterval, fetchOrdersInterval;
const pairs = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'SOLUSDT', 'XRPUSDT', 'ADAUSDT', 'DOGEUSDT', 'AVAXUSDT', 'DOTUSDT', 'MATICUSDT', 'LTCUSDT', 'TRXUSDT', 'LINKUSDT', 'UNIUSDT', 'ATOMUSDT', 'ETCUSDT', 'BCHUSDT', 'FILUSDT', 'ICPUSDT', 'NEARUSDT', 'AAVEUSDT', 'ALGOUSDT'];
let fetchOrdersInterval;
let activeOrdersTab = 'pending';
let countdownInterval;
const dom = {
currIcon: document.getElementById('curr-icon'),
@ -253,52 +333,52 @@ document.addEventListener('DOMContentLoaded', function() {
currChange: document.getElementById('curr-change'),
hHigh: document.getElementById('h-high'),
hLow: document.getElementById('h-low'),
tvChartContainer: document.getElementById('tv_chart_container'),
pairsList: document.getElementById('pairs-list'),
pairSearch: document.getElementById('pair-search'),
asksList: document.getElementById('asks-list'),
bidsList: document.getElementById('bids-list'),
priceDisplay: document.getElementById('current-price-display'),
obAmountLabel: document.getElementById('ob-amount-label'),
midPrice: document.getElementById('price-mid'),
optionAmountInput: document.getElementById('option-amount'),
potentialProfit: document.getElementById('potential-profit'),
usdtBalance: document.getElementById('usdt-balance'),
recordsList: document.getElementById('records-list'),
modal: document.getElementById('option-modal'),
modalHeader: document.getElementById('modal-header'),
modalOpenPrice: document.getElementById('modal-open-price'),
// Modal DOM
modal: document.getElementById('countdown-modal'),
modalSymbol: document.getElementById('modal-symbol'),
modalTimer: document.getElementById('countdown-timer'),
modalCircle: document.getElementById('circle-bar'),
modalCurrPrice: document.getElementById('modal-curr-price'),
modalPeriod: document.getElementById('modal-period'),
modalDirection: document.getElementById('modal-direction'),
modalAmount: document.getElementById('modal-amount'),
modalCurrentPrice: document.getElementById('modal-current-price'),
modalPnl: document.getElementById('modal-pnl'),
modalStartTime: document.getElementById('modal-start-time'),
modalEndTime: document.getElementById('modal-end-time'),
modalFooter: document.getElementById('modal-footer'),
timerCountdown: document.getElementById('timer-countdown'),
timerProgress: document.getElementById('timer-progress'),
modalPrice: document.getElementById('modal-price'),
modalProfit: document.getElementById('modal-profit'),
resultOverlay: document.getElementById('result-overlay'),
resultIcon: document.getElementById('result-icon'),
resultTitle: document.getElementById('result-title'),
resultAmount: document.getElementById('result-amount')
};
function updateAvailableBalance() {
const usdtBalance = parseFloat(userAssets.USDT || 0).toFixed(2);
dom.usdtBalance.innerText = usdtBalance;
dom.usdtBalance.innerText = parseFloat(userAssets.USDT || 0).toFixed(2);
}
function initChartWidget(symbol) {
if (chartWidget) { chartWidget = null; }
if (dom.tvChartContainer.firstChild) dom.tvChartContainer.innerHTML = '';
document.getElementById('tv_chart_container').innerHTML = '';
chartWidget = new TradingView.widget({
"autosize": true, "symbol": `BINANCE:${symbol}`,
"interval": "1", "timezone": "Etc/UTC", "theme": "dark", "style": "3",
"interval": "1", "timezone": "Etc/UTC", "theme": "dark", "style": "1",
"locale": "<?php echo $lang == 'zh' ? 'zh_CN' : 'en'; ?>",
"toolbar_bg": "#161a1e", "enable_publishing": false, "hide_side_toolbar": true, "allow_symbol_change": false,
"container_id": "tv_chart_container",
"studies": [],
"studies": ["Volume@tv-basicstudies"],
"overrides": {
"paneProperties.background": "#161a1e",
"paneProperties.vertGridProperties.color": "rgba(255, 255, 255, 0.0)",
"paneProperties.horzGridProperties.color": "rgba(255, 255, 255, 0.0)",
"paneProperties.vertGridProperties.color": "rgba(255, 255, 255, 0.03)",
"paneProperties.horzGridProperties.color": "rgba(255, 255, 255, 0.03)",
"scalesProperties.textColor" : "#848e9c",
"mainSeriesProperties.style": 3,
"mainSeriesProperties.lineStyle.color": "#007aff",
}
});
}
@ -306,81 +386,70 @@ document.addEventListener('DOMContentLoaded', function() {
function connectWebSocket() {
if (ws) ws.close();
const tickerStreams = pairs.map(p => `${p.toLowerCase()}@ticker`).join('/');
const depthStream = `${currentPair.toLowerCase()}@depth5@100ms`;
const depthStream = `${currentPair.toLowerCase()}@depth20@100ms`;
ws = new WebSocket(`wss://stream.binance.com:9443/stream?streams=${tickerStreams}/${depthStream}`);
ws.onmessage = (event) => {
const { stream, data } = JSON.parse(event.data);
if (!data) return;
if (stream.endsWith('@ticker')) {
marketData[data.s] = data;
if (data.s === currentPair) {
currentPrice = parseFloat(data.c);
updatePriceUI(data);
dom.priceDisplay.innerText = currentPrice.toLocaleString('en-US', {minimumFractionDigits: 4, maximumFractionDigits: 4});
dom.priceDisplay.style.color = data.P >= 0 ? '#0ecb81' : '#f6465d';
dom.midPrice.innerText = currentPrice.toFixed(2);
dom.midPrice.style.color = data.P >= 0 ? '#0ecb81' : '#f6465d';
if (dom.modal.style.display === 'flex') {
dom.modalCurrPrice.innerText = currentPrice.toFixed(4);
dom.modalCurrPrice.style.color = data.P >= 0 ? '#0ecb81' : '#f6465d';
}
}
if (dom.pairsList.offsetParent) renderPairs();
} else if (stream.endsWith('@depth5@100ms')) {
} else if (stream.endsWith('@depth20@100ms')) {
renderOrderBook(data.bids, data.asks);
}
};
ws.onerror = (error) => console.error("WebSocket Error:", error);
ws.onclose = () => setTimeout(connectWebSocket, 5000);
}
function renderOrderBook(bids, asks) {
const formatRow = (row, type) => {
const price = parseFloat(row[0]);
const amount = parseFloat(row[1]);
return `<div class="book-row">
<div class="price ${type}">${price.toFixed(4)}</div>
<div>${amount.toFixed(4)}</div>
<div>${(price * amount).toFixed(2)}</div>
</div>`;
};
dom.asksList.innerHTML = asks.slice(0, 8).reverse().map(ask => formatRow(ask, 'down')).join('');
dom.bidsList.innerHTML = bids.slice(0, 8).map(bid => formatRow(bid, 'up')).join('');
const formatRow = (row, type) => `<div class="ob-row"><div class="price ${type}">${parseFloat(row[0]).toFixed(2)}</div><div>${parseFloat(row[1]).toFixed(3)}</div></div>`;
dom.asksList.innerHTML = asks.slice(0, 20).reverse().map(a => formatRow(a, 'down')).join('');
dom.bidsList.innerHTML = bids.slice(0, 20).map(b => formatRow(b, 'up')).join('');
}
function updatePriceUI(d) {
if (!d) return;
const color = d.P >= 0 ? '#0ecb81' : '#f6465d';
const priceStr = parseFloat(d.c).toLocaleString('en-US', {minimumFractionDigits: 4, maximumFractionDigits: 4});
document.title = `${priceStr} | ${d.s.replace('USDT', '/USDT')}`;
const priceStr = parseFloat(d.c).toLocaleString('en-US', {minimumFractionDigits: 2});
dom.currPrice.innerText = priceStr;
dom.currPrice.style.color = color;
dom.currChange.innerText = `${d.P >= 0 ? '+' : ''}${parseFloat(d.P).toFixed(2)}%`;
dom.currChange.style.color = color;
dom.hHigh.innerText = parseFloat(d.h).toLocaleString('en-US', {minimumFractionDigits: 4});
dom.hLow.innerText = parseFloat(d.l).toLocaleString('en-US', {minimumFractionDigits: 4});
dom.hHigh.innerText = parseFloat(d.h).toFixed(2);
dom.hLow.innerText = parseFloat(d.l).toFixed(2);
document.title = `${priceStr} | ${currentPair}`;
}
function renderPairs() {
const query = dom.pairSearch.value.toUpperCase();
const filteredPairs = pairs.filter(p => p.includes(query));
dom.pairsList.innerHTML = filteredPairs.map(p => {
dom.pairsList.innerHTML = pairs.filter(p => p.includes(query)).map(p => {
const d = marketData[p] || {};
const icon = `https://assets.coincap.io/assets/icons/${p.replace('USDT','').toLowerCase()}@2x.png`;
const price = d.c ? parseFloat(d.c).toLocaleString('en-US', {minimumFractionDigits: 4}) : '--';
const price = d.c ? parseFloat(d.c).toFixed(2) : '--';
const change = d.P ? `${parseFloat(d.P) >= 0 ? '+' : ''}${parseFloat(d.P).toFixed(2)}%` : '--';
const color = (d.P && parseFloat(d.P) >= 0) ? '#0ecb81' : '#f6465d';
return `<div class="pair-item ${p === currentPair ? 'active' : ''}" data-pair="${p}">
<img src="${icon}" class="coin-icon" onerror="this.style.display='none'">
<div style="flex:1"><div style="font-weight:600; font-size:13px">${p.replace('USDT','/USDT')}</div></div>
<div style="text-align:right"><div style="font-size:13px; font-weight:600">${price}</div><div style="font-size:11px; color:${color}">${change}</div></div>
<img src="https://assets.coincap.io/assets/icons/${p.replace('USDT','').toLowerCase()}@2x.png" class="coin-icon" onerror="this.style.opacity=0">
<div style="flex:1"><div style="font-weight:600; color:white;">${p.replace('USDT','/USDT')}</div></div>
<div style="text-align:right"><div style="font-weight:600; color:white;">${price}</div><div style="font-size:11px; color:${color}">${change}</div></div>
</div>`;
}).join('');
}
function switchPair(pair) {
currentPair = pair;
const coinName = pair.replace('USDT', '');
dom.obAmountLabel.textContent = `<?php echo __('amount'); ?> (${coinName})`;
dom.currPair.innerText = `${coinName}/USDT`;
dom.currIcon.src = `https://assets.coincap.io/assets/icons/${coinName.toLowerCase()}@2x.png`;
updatePriceUI(marketData[pair]);
dom.currPair.innerText = `${pair.replace('USDT', '/USDT')}`;
dom.currIcon.src = `https://assets.coincap.io/assets/icons/${pair.replace('USDT','').toLowerCase()}@2x.png`;
initChartWidget(pair);
renderPairs();
connectWebSocket();
@ -388,77 +457,159 @@ document.addEventListener('DOMContentLoaded', function() {
function updatePotentialProfit() {
const amount = parseFloat(dom.optionAmountInput.value) || 0;
const profit = amount * (selectedRate / 100);
dom.potentialProfit.innerText = `+${profit.toFixed(2)}`;
dom.potentialProfit.innerText = `+${(amount * (selectedRate / 100)).toFixed(2)}`;
}
function handleDurationChange(btn) {
window.closeCountdownModal = () => {
dom.modal.style.display = 'none';
dom.resultOverlay.style.display = 'none';
if (countdownInterval) clearInterval(countdownInterval);
};
function startCountdown(duration, orderId) {
let timeLeft = duration;
const total = duration;
dom.modalTimer.innerText = `00:${timeLeft < 10 ? '0' + timeLeft : timeLeft}`;
dom.modalCircle.style.strokeDashoffset = 0;
if (countdownInterval) clearInterval(countdownInterval);
countdownInterval = setInterval(async () => {
timeLeft--;
if (timeLeft <= 0) {
clearInterval(countdownInterval);
dom.modalTimer.innerText = "00:00";
dom.modalCircle.style.strokeDashoffset = 408;
// Show final result
try {
const r = await fetch(`api/get_option_orders.php?status=completed`);
const orders = await r.json();
const order = orders.find(o => o.id == orderId);
if (order) {
const profit = parseFloat(order.profit);
dom.resultIcon.innerHTML = profit > 0 ? '<i class="fas fa-trophy" style="color:#0ecb81"></i>' : '<i class="fas fa-times-circle" style="color:#f6465d"></i>';
dom.resultTitle.innerText = profit > 0 ? '<?php echo __('win'); ?>' : '<?php echo __('loss'); ?>';
dom.resultTitle.style.color = profit > 0 ? '#0ecb81' : '#f6465d';
dom.resultAmount.innerText = (profit > 0 ? '+' : '') + profit.toFixed(2) + ' USDT';
dom.resultAmount.style.color = profit > 0 ? '#0ecb81' : '#f6465d';
dom.resultOverlay.style.display = 'flex';
} else {
// Fallback if order not found immediately
setTimeout(() => startCountdown(0, orderId), 2000);
}
} catch(e) {}
return;
}
const minutes = Math.floor(timeLeft / 60);
const seconds = timeLeft % 60;
dom.modalTimer.innerText = `${minutes < 10 ? '0' + minutes : minutes}:${seconds < 10 ? '0' + seconds : seconds}`;
const offset = (408 * (total - timeLeft) / total);
dom.modalCircle.style.strokeDashoffset = offset;
}, 1000);
}
async function placeOrder(direction) {
const amount = parseFloat(dom.optionAmountInput.value);
if (isNaN(amount) || amount < minAmount) { alert("<?php echo __('min_amount'); ?>: " + minAmount + " USDT"); return; }
const btn = direction === 'up' ? document.getElementById('btn-buy-up') : document.getElementById('btn-buy-down');
const originalText = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
try {
const response = await fetch('api/place_option_order.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
pair: currentPair, amount: amount, direction: direction,
duration: selectedDuration, rate: selectedRate, price: currentPrice
})
});
const result = await response.json();
if (result.success) {
// Show Modal
dom.modalSymbol.innerText = currentPair.replace('USDT', '');
dom.modalPeriod.innerText = selectedDuration + 's';
dom.modalDirection.innerText = direction === 'up' ? '<?php echo __('buy_up'); ?>' : '<?php echo __('buy_down'); ?>';
dom.modalDirection.className = 'modal-info-value ' + (direction === 'up' ? 'up' : 'down');
dom.modalAmount.innerText = amount + ' USDT';
dom.modalPrice.innerText = currentPrice.toFixed(4);
dom.modalProfit.innerText = '+' + (amount * (selectedRate / 100)).toFixed(2) + ' USDT';
dom.modalProfit.className = 'modal-info-value up';
dom.modal.style.display = 'flex';
dom.resultOverlay.style.display = 'none';
startCountdown(selectedDuration, result.order.id);
fetchOrders();
} else {
alert(result.error || "<?php echo __('error_placing_order'); ?>");
}
} catch(e) { alert("<?php echo __('network_error'); ?>"); }
finally { btn.disabled = false; btn.innerHTML = originalText; }
}
async function fetchOrders() {
try {
const r = await fetch(`api/get_option_orders.php?status=${activeOrdersTab}`);
const orders = await r.json();
renderOrders(orders);
} catch (e) {}
}
function renderOrders(orders) {
if (!orders || orders.length === 0) {
dom.recordsList.innerHTML = '<tr><td colspan="8" style="text-align:center; padding:40px; color:#848e9c"><?php echo __('no_records'); ?></td></tr>';
return;
}
let html = `<thead><tr><th><?php echo __('pair'); ?></th><th><?php echo __('direction'); ?></th><th><?php echo __('amount'); ?></th><th><?php echo __('opening_price'); ?></th><th><?php echo __('closing_price'); ?></th><th><?php echo __('profit'); ?></th><th><?php echo __('status'); ?></th><th><?php echo __('time'); ?></th></tr></thead><tbody>`;
orders.forEach(o => {
const color = o.direction === 'up' ? '#0ecb81' : '#f6465d';
const statusColor = o.status === 'completed' ? (parseFloat(o.profit) > 0 ? '#0ecb81' : '#f6465d') : '#ffc107';
html += `<tr>
<td style="font-weight:600">${o.symbol}</td>
<td style="color:${color}">${o.direction === 'up' ? '<?php echo __('buy_up'); ?>' : '<?php echo __('buy_down'); ?>'}</td>
<td>${parseFloat(o.amount).toFixed(2)}</td>
<td>${parseFloat(o.opening_price).toFixed(4)}</td>
<td>${o.closing_price ? parseFloat(o.closing_price).toFixed(4) : '--'}</td>
<td style="color:${statusColor}">${o.status === 'completed' ? (parseFloat(o.profit) > 0 ? '+' : '') + parseFloat(o.profit).toFixed(2) : '--'}</td>
<td style="color:${statusColor}">${o.status === 'completed' ? (parseFloat(o.profit) > 0 ? '<?php echo __('win'); ?>' : '<?php echo __('loss'); ?>') : '<?php echo __('in_progress'); ?>'}</td>
<td style="color:#848e9c">${o.created_at}</td>
</tr>`;
});
dom.recordsList.innerHTML = html + '</tbody>';
}
dom.pairsList.addEventListener('click', e => { const item = e.target.closest('.pair-item'); if (item) switchPair(item.dataset.pair); });
dom.pairSearch.addEventListener('input', renderPairs);
document.querySelectorAll('.time-btn').forEach(btn => btn.addEventListener('click', () => {
document.querySelector('.time-btn.active').classList.remove('active');
btn.classList.add('active');
selectedDuration = parseInt(btn.dataset.duration);
selectedRate = parseInt(btn.dataset.rate);
minAmount = parseInt(btn.dataset.min);
dom.optionAmountInput.placeholder = `<?php echo __('min_amount_is_pl'); ?> ${minAmount}`;
dom.optionAmountInput.placeholder = "<?php echo __('min_amount'); ?> " + minAmount;
updatePotentialProfit();
}
async function placeOrder(direction) {
const amount = parseFloat(dom.optionAmountInput.value);
if (isNaN(amount) || amount < minAmount) {
alert(`<?php echo __('min_amount_is'); ?> ${minAmount} USDT`);
return;
}
const response = await fetch('api/place_option_order.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
pair: currentPair, amount: amount, direction: direction,
duration: selectedDuration, rate: selectedRate
})
});
const result = await response.json();
if (result.success && result.order) {
showCountdownModal(result.order);
fetchOrders(); // Refresh orders list
} else {
alert(`<?php echo __('order_failed'); ?>: ${result.error || 'Unknown error'}`);
}
}
async function fetchOrders() {
const response = await fetch(`api/get_option_orders.php?status=${activeOrdersTab}`);
const orders = await response.json();
renderOrders(orders);
}
function renderOrders(orders) { /* Unchanged */ }
function showCountdownModal(order) { /* Unchanged */ }
// --- Event Listeners ---
dom.pairsList.addEventListener('click', e => { e.target.closest('.pair-item')?.dataset.pair && switchPair(e.target.closest('.pair-item').dataset.pair); });
dom.pairSearch.addEventListener('input', renderPairs);
document.querySelectorAll('.time-btn').forEach(btn => btn.addEventListener('click', () => handleDurationChange(btn)));
}));
dom.optionAmountInput.addEventListener('input', updatePotentialProfit);
document.getElementById('btn-buy-up').addEventListener('click', () => placeOrder('up'));
document.getElementById('btn-buy-down').addEventListener('click', () => placeOrder('down'));
document.querySelectorAll('.record-tab').forEach(tab => {
tab.addEventListener('click', function() {
document.querySelector('.record-tab.active').classList.remove('active');
this.classList.add('active');
activeOrdersTab = this.dataset.status;
fetchOrders();
});
});
document.querySelectorAll('.record-tab').forEach(tab => tab.addEventListener('click', () => {
document.querySelector('.record-tab.active').classList.remove('active');
tab.classList.add('active');
activeOrdersTab = tab.dataset.status;
fetchOrders();
}));
// --- Initial Load ---
updateAvailableBalance();
switchPair('BTCUSDT');
updatePotentialProfit();
fetchOrders(); // Initial fetch
fetchOrdersInterval = setInterval(fetchOrders, 5000); // Poll for updates
fetchOrders();
setInterval(fetchOrders, 5000);
});
</script>
<?php include 'footer.php'; ?>
<?php include 'footer.php'; ?>

553
spot.php
View File

@ -1,477 +1,316 @@
<?php
include 'header.php';
require_once 'db/config.php';
require_once 'includes/i18n.php';
$user_id = $_SESSION['user_id'] ?? null;
$user_assets = [];
if ($user_id) {
$pdo = db();
$stmt = $pdo->prepare("SELECT currency, balance FROM assets WHERE user_id = ?");
$stmt = $db->prepare("SELECT balance FROM users WHERE id = ?");
$stmt->execute([$user_id]);
$usdt_balance = $stmt->fetchColumn();
$user_assets['USDT'] = (float)($usdt_balance ?: 0);
$stmt = $db->prepare("SELECT symbol, amount FROM user_assets WHERE user_id = ?");
$stmt->execute([$user_id]);
$assets_data = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
if ($assets_data) {
$user_assets = $assets_data;
foreach ($assets_data as $symbol => $amount) {
$user_assets[$symbol] = (float)$amount;
}
}
}
?>
<style>
/* New Robust Layout */
html, body {
/* height: 100%; */
/* overflow: hidden; */ /* UNLOCKED for scrolling */
}
.trading-page-wrapper {
/* height: 100vh; */ /* UNLOCKED for scrolling */
display: flex;
flex-direction: column;
min-height: 100vh; /* Ensure it takes at least full height */
}
.trading-page-wrapper .navbar {
flex-shrink: 0;
}
.trading-container {
flex-grow: 1;
display: flex;
min-height: 800px; /* Give a minimum height for content */
background: #0b0e11;
}
.trading-page-wrapper { display: flex; flex-direction: column; min-height: 100vh; background: #0b0e11; }
.trading-container { flex-grow: 1; display: flex; min-height: 800px; max-width: 1920px; margin: 0 auto; width: 100%; }
.left-col { width: 280px; flex-shrink: 0; background: #161a1e; border-right: 1px solid #2b3139; display: flex; flex-direction: column; }
.center-col { flex: 1; min-width: 600px; display: flex; flex-direction: column; border-right: 1px solid #2b3139; }
.right-col { width: 300px; flex-shrink: 0; background: #161a1e; display: flex; flex-direction: column; }
/* Columns */
.left-col { width: 260px; flex-shrink: 0; background: #161a1e; display: flex; flex-direction: column; border-right: 1px solid #2b3139; }
.center-col { flex: 1; min-width: 600px; display: flex; flex-direction: column; border-right: 1px solid #2b3139; min-height: 0; }
.right-col { width: 320px; flex-shrink: 0; background: #161a1e; display: flex; flex-direction: column; }
.chart-header { height: 55px; padding: 0 20px; display: flex; align-items: center; background: #161a1e; border-bottom: 1px solid #2b3139; gap: 20px; flex-shrink: 0; }
.chart-box { height: 450px; border-bottom: 1px solid #2b3139; flex-shrink: 0; }
.bottom-content-wrapper { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
/* Center Column Content Layout */
.chart-header { flex-shrink: 0; height: 50px; padding: 8px 20px; display: flex; align-items: center; background: #161a1e; border-bottom: 1px solid #2b3139; gap: 20px; }
.chart-box { height: 350px; flex-shrink: 0; border-bottom: 1px solid #2b3139; }
.bottom-content-wrapper { flex-grow: 1; overflow-y: auto; min-height: 0; }
.order-box { padding: 12px 20px; background: #161a1e; border-bottom: 1px solid #2b3139; }
.records-container { display: flex; flex-direction: column; background: #161a1e; flex-grow: 1; min-height: 400px; }
.record-tabs { display: flex; padding: 0 20px; flex-shrink: 0; border-bottom: 1px solid #2b3139; }
.record-tab { padding: 12px 0; margin-right: 25px; font-size: 14px; font-weight: 600; color: #848e9c; cursor: pointer; border-bottom: 2px solid transparent; }
.record-tab.active { color: white; border-bottom-color: var(--primary-color); }
#records-list { padding: 10px 0; flex-grow: 1; overflow-y: auto; }
.order-box { padding: 20px; background: #161a1e; border-bottom: 1px solid #2b3139; flex-shrink: 0; }
.records-container { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
.record-tabs { display: flex; padding: 0 20px; border-bottom: 1px solid #2b3139; flex-shrink: 0; }
.record-tab { padding: 12px 0; margin-right: 25px; font-size: 13px; font-weight: 600; color: #848e9c; cursor: pointer; position: relative; }
.record-tab.active { color: white; }
.record-tab.active::after { content: ''; position: absolute; bottom: 0; left: 0; width: 100%; height: 2px; background: var(--primary-color); }
.right-col { display: flex; flex-direction: column;}
.ob-panel, .trades-panel { flex-grow: 1; min-height: 0; display: flex; flex-direction: column;}
.ob-content { flex: 1; min-height: 0; display: flex; flex-direction: column; }
#asks-list { flex: 1 1 50%; display: flex; flex-direction: column; overflow: hidden; justify-content: flex-end; }
#bids-list { flex: 1 1 50%; display: flex; flex-direction: column; overflow: hidden; justify-content: flex-start; }
#trades-content { flex: 1; overflow-y: auto; min-height: 0; }
.left-col { overflow-y: auto; }
#pairs-list { flex: 1; overflow-y: auto; }
.ob-header, .trades-header { padding: 10px 15px; font-size: 12px; color: #848e9c; display: flex; justify-content: space-between; border-bottom: 1px solid #2b3139; background: #161a1e; flex-shrink: 0;}
.ob-row, .trade-row { display: flex; justify-content: space-between; padding: 3px 15px; font-size: 12px; position: relative; cursor: pointer; height: 20px; align-items: center; flex-shrink: 0; }
.ob-bar { position: absolute; right: 0; top: 0; bottom: 0; opacity: 0.1; z-index: 0; transition: width 0.1s linear; }
#mid-price { padding: 8px 15px; font-size: 16px; font-weight: 800; text-align: center; border-top: 1px solid #2b3139; border-bottom: 1px solid #2b3139; background: #161a1e; flex-shrink: 0;}
.coin-icon { width: 22px; height: 22px; margin-right: 12px; border-radius: 50%; }
.stats-item { display: flex; flex-direction: column; justify-content: center; }
.stats-label { font-size: 10px; color: #848e9c; margin-bottom: 1px; }
.stats-value { font-size: 12px; font-weight: 600; color: white; }
#websocket-status { display: none; /* Hiding this element as requested */ padding: 8px 15px; font-size: 12px; color: #fff; text-align: center; position: absolute; top: 0; left: 0; right: 0; z-index: 1000; }
#records-list-container { height: 350px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #2b3139 transparent; }
#records-list-container::-webkit-scrollbar { width: 4px; }
#records-list-container::-webkit-scrollbar-thumb { background: #2b3139; border-radius: 10px; }
table { width: 100%; font-size: 12px; border-collapse: collapse; }
th { color: #848e9c; font-weight: 500; text-align: left; padding: 10px 15px; border-bottom: 1px solid #2b3139; background: #161a1e; position: sticky; top: 0; z-index: 10; }
td { padding: 10px 15px; border-bottom: 1px solid rgba(255,255,255,0.02); color: #eaecef; height: 38px; }
.category-tabs { display: flex; padding: 15px 15px 5px; gap: 8px; }
.category-tab { flex: 1; text-align: center; padding: 6px 0; background: #2b3139; border-radius: 4px; font-size: 12px; color: #848e9c; cursor: pointer; transition: all 0.2s; border: 1px solid transparent; }
.category-tab { flex: 1; text-align: center; padding: 6px 0; background: #2b3139; border-radius: 4px; font-size: 12px; color: #848e9c; cursor: pointer; border: 1px solid transparent; }
.category-tab.active { background: rgba(0, 82, 255, 0.1); border-color: var(--primary-color); color: var(--primary-color); font-weight: bold; }
.search-box { padding: 5px 15px 10px; position: relative; }
.search-box i { position: absolute; left: 25px; top: 50%; transform: translateY(-50%); color: #848e9c; font-size: 12px; }
.search-box input { width: 100%; background: #2b3139; border: 1px solid transparent; border-radius: 4px; padding: 8px 10px 8px 30px; color: white; font-size: 13px; outline: none; }
.pair-item { display: flex; align-items: center; padding: 10px 15px; cursor: pointer; transition: background 0.2s; border-bottom: 1px solid rgba(255,255,255,0.02); }
.pair-item.active { background: #1e2329; }
.order-type-tabs { display: flex; gap: 20px; margin-bottom: 10px; }
.order-type-tab { font-size: 13px; color: #848e9c; cursor: pointer; padding-bottom: 4px; border-bottom: 2px solid transparent; }
.order-type-tab.active { color: white; border-bottom-color: var(--primary-color); font-weight: bold; }
.input-group { background: #2b3139; border-radius: 4px; padding: 6px 12px; display: flex; align-items: center; margin-bottom: 6px; border: 1px solid transparent; }
.search-box { padding: 10px 15px 15px; position: relative; }
.search-box i { position: absolute; left: 25px; top: 22px; color: #848e9c; font-size: 12px; }
.search-box input { width: 100%; background: #2b3139; border: 1px solid transparent; border-radius: 6px; padding: 8px 10px 8px 35px; color: white; font-size: 13px; outline: none; transition: 0.2s; }
.search-box input:focus { border-color: var(--primary-color); }
#pairs-list { height: 684px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #2b3139 transparent; }
#pairs-list::-webkit-scrollbar { width: 4px; }
#pairs-list::-webkit-scrollbar-thumb { background: #2b3139; border-radius: 10px; }
.pair-item { display: flex; align-items: center; padding: 10px 15px; cursor: pointer; border-bottom: 1px solid rgba(255,255,255,0.02); height: 38px; }
.pair-item.active { background: #1e2329; border-left: 3px solid var(--primary-color); }
.coin-icon { width: 22px; height: 22px; margin-right: 12px; border-radius: 50%; }
.input-group { background: #2b3139; border-radius: 4px; padding: 8px 12px; display: flex; align-items: center; margin-bottom: 10px; border: 1px solid #3c424a; }
.input-group:focus-within { border-color: var(--primary-color); }
.input-group input { background: transparent; border: none; color: white; flex: 1; outline: none; font-size: 13px; width: 100%; }
.input-group .label { color: #848e9c; font-size: 11px; margin-right: 10px; }
.input-group .unit { color: #848e9c; margin-left: 8px; font-size: 11px; }
.slider-container { margin: 8px 0 15px; padding: 0 5px; }
input[type=range] { -webkit-appearance: none; width: 100%; height: 3px; background: #2b3139; border-radius: 2px; outline: none; }
input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 14px; height: 14px; background: var(--primary-color); border-radius: 50%; cursor: pointer; }
.trade-btn { width: 100%; padding: 10px; border-radius: 4px; font-weight: bold; font-size: 14px; border: none; cursor: pointer; margin-top: 5px; }
.btn-buy { background: #0ecb81; color: white; }
.btn-sell { background: #f6465d; color: white; }
.right-col-tabs { display: flex; border-bottom: 1px solid #2b3139; flex-shrink: 0;}
.right-col-tab { flex: 1; text-align: center; padding: 10px 0; font-size: 13px; font-weight: 600; color: #848e9c; cursor: pointer; background: #161a1e; }
.right-col-tab.active { color: white; background: #1e2329;}
.input-group input { background: transparent; border: none; color: white; flex: 1; outline: none; font-size: 14px; width: 100%; }
.input-group .label { color: #848e9c; font-size: 12px; margin-right: 10px; min-width: 40px; }
.input-group .unit { color: #848e9c; margin-left: 8px; font-size: 12px; }
.slider-container { margin: 15px 0 20px; }
input[type=range] { width: 100%; height: 4px; background: #2b3139; border-radius: 2px; outline: none; }
.trade-btn { width: 100%; padding: 12px; border-radius: 4px; font-weight: bold; font-size: 15px; border: none; cursor: pointer; color: white; transition: 0.2s; }
.btn-buy { background: #0ecb81; }
.btn-sell { background: #f6465d; }
.btn-buy:hover, .btn-sell:hover { filter: brightness(1.1); }
.right-col-tabs { display: flex; border-bottom: 1px solid #2b3139; flex-shrink: 0; }
.right-col-tab { flex: 1; text-align: center; padding: 12px 0; font-size: 13px; font-weight: 600; color: #848e9c; cursor: pointer; }
.right-col-tab.active { color: white; background: #1e2329; }
.ob-header { display: flex; justify-content: space-between; padding: 8px 15px; font-size: 11px; color: #848e9c; border-bottom: 1px solid #2b3139; }
.ob-row { display: flex; justify-content: space-between; padding: 3px 15px; font-size: 12px; position: relative; cursor: pointer; }
#ob-panel-content { flex: 1; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #2b3139 transparent; }
#ob-panel-content::-webkit-scrollbar { width: 4px; }
#ob-panel-content::-webkit-scrollbar-thumb { background: #2b3139; border-radius: 10px; }
</style>
<div class="trading-page-wrapper">
<?php // The real header is included in the file, this is a placeholder for the structure. We will inject the actual header.php content. ?>
<div class="trading-container">
<div class="left-col d-none d-lg-flex flex-column">
<!-- Left Column -->
<div class="left-col d-none d-lg-flex">
<div class="category-tabs">
<div class="category-tab" onclick="location.href='options.php'"><?php echo __('nav_options'); ?></div>
<div class="category-tab active" onclick="location.href='spot.php'"><?php echo __('nav_spot'); ?></div>
<div class="category-tab" onclick="location.href='futures.php'"><?php echo __('nav_futures'); ?></div>
</div>
<div class="search-box">
<i class="fas fa-search"></i>
<input type="text" id="pair-search" placeholder="<?php echo __('search_currency'); ?>">
</div>
<div id="pairs-list" class="flex-grow-1" style="overflow-y: auto;"></div>
<div class="search-box"><i class="fas fa-search"></i><input type="text" id="pair-search" placeholder="<?php echo __('search_currency'); ?>"></div>
<div id="pairs-list"></div>
</div>
<!-- Center Column -->
<div class="center-col">
<div id="websocket-status"></div>
<div class="chart-header">
<div style="display: flex; align-items: center; gap: 10px; padding-right: 15px; border-right: 1px solid #2b3139;">
<img id="curr-icon" src="" class="coin-icon" style="margin:0; width:28px; height:28px;">
<span id="curr-pair" style="font-weight: 800; font-size: 16px;">--/--</span>
<div style="display: flex; align-items: center; gap: 10px;">
<img id="curr-icon" src="" class="coin-icon">
<span id="curr-pair" style="font-weight: 800; font-size: 18px; color:white;">--/--</span>
</div>
<div class="stats-item">
<div id="curr-price" style="font-size: 18px; font-weight: 800; color: #0ecb81; line-height: 1.2;">--</div>
<div id="curr-change" style="font-size: 11px; font-weight: 600;">--</div>
<div id="curr-price" style="font-size: 20px; font-weight: 800; color: #0ecb81;">--</div>
<div id="curr-change" style="font-size: 12px; font-weight: 600;">--</div>
</div>
<div class="stats-item"><span class="stats-label"><?php echo __('24h_high'); ?></span><span class="stats-value" id="h-high">--</span></div>
<div class="stats-item"><span class="stats-label"><?php echo __('24h_low'); ?></span><span class="stats-value" id="h-low">--</span></div>
<div class="stats-item"><span class="stats-label"><?php echo __('24h_vol'); ?></span><span class="stats-value" id="h-vol">--</span></div>
</div>
<div class="chart-box" id="tv_chart_container"></div>
<div class="bottom-content-wrapper">
<div class="order-box">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 25px;">
<div>
<div class="order-type-tabs" data-side="buy">
<div class="order-type-tab active" data-type="limit"><?php echo __('limit'); ?></div>
<div class="order-type-tab" data-type="market"><?php echo __('market'); ?></div>
</div>
<div class="input-group"><span class="label"><?php echo __('price'); ?></span><input type="number" id="buy-price" placeholder="0.00"><span class="unit">USDT</span></div>
<div class="input-group"><span class="label"><?php echo __('amount'); ?></span><input type="number" id="buy-amount" placeholder="0.00"><span class="unit coin-name">--</span></div>
<div class="slider-container"><input type="range" id="buy-slider" min="0" max="100" value="0" step="1"></div>
<div style="font-size:11px; color:#848e9c; margin-bottom:8px; display:flex; justify-content:space-between"><span><?php echo __('available'); ?>:</span><span id="avail-usdt" style="font-weight:bold; color:white;">0.00 USDT</span></div>
<div style="display: flex; gap: 20px; margin-bottom: 15px;">
<div class="record-tab active"><?php echo __('market'); ?></div>
</div>
<div class="input-group"><span class="label"><?php echo __('price'); ?></span><input type="text" placeholder="<?php echo __('market'); ?>" disabled><span class="unit">USDT</span></div>
<div class="input-group"><span class="label"><?php echo __('amount'); ?></span><input type="number" id="spot-amount" step="0.0001" placeholder="0.00"><span class="unit coin-name">--</span></div>
<div class="slider-container"><input type="range" id="spot-slider" min="0" max="100" value="0"></div>
<div style="display: flex; gap: 15px;">
<div style="flex: 1;">
<div style="font-size: 12px; color: #848e9c; margin-bottom: 8px;"><?php echo __('available'); ?>: <span id="avail-usdt" style="color:white; font-weight:600">0.00</span> USDT</div>
<button id="btn-buy" class="trade-btn btn-buy"><?php echo __('buy'); ?> <span class="coin-name">--</span></button>
</div>
<div>
<div class="order-type-tabs" data-side="sell">
<div class="order-type-tab active" data-type="limit"><?php echo __('limit'); ?></div>
<div class="order-type-tab" data-type="market"><?php echo __('market'); ?></div>
</div>
<div class="input-group"><span class="label"><?php echo __('price'); ?></span><input type="number" id="sell-price" placeholder="0.00"><span class="unit">USDT</span></div>
<div class="input-group"><span class="label"><?php echo __('amount'); ?></span><input type="number" id="sell-amount" placeholder="0.00"><span class="unit coin-name">--</span></div>
<div class="slider-container"><input type="range" id="sell-slider" min="0" max="100" value="0" step="1"></div>
<div style="font-size:11px; color:#848e9c; margin-bottom:8px; display:flex; justify-content:space-between"><span><?php echo __('available'); ?>:</span><span id="avail-coin" style="font-weight:bold; color:white;">0.00 --</span></div>
<div style="flex: 1;">
<div style="font-size: 12px; color: #848e9c; margin-bottom: 8px;"><?php echo __('available'); ?>: <span id="avail-coin" style="color:white; font-weight:600">0.00</span> <span class="coin-name">--</span></div>
<button id="btn-sell" class="trade-btn btn-sell"><?php echo __('sell'); ?> <span class="coin-name">--</span></button>
</div>
</div>
</div>
<div class="records-container">
<div class="record-tabs">
<div class="record-tab active" data-status="open"><?php echo __('current_orders'); ?></div>
<div class="record-tab active" data-status="open"><?php echo __('open_orders'); ?></div>
<div class="record-tab" data-status="history"><?php echo __('history_orders'); ?></div>
</div>
<div id="records-list"></div>
<div id="records-list-container">
<table style="width:100%;" id="records-table"></table>
</div>
</div>
</div>
</div>
<div class="right-col d-none d-lg-flex">
<!-- Right Column -->
<div class="right-col d-none d-xl-flex">
<div class="right-col-tabs">
<div class="right-col-tab active" data-tab="ob"><?php echo __('order_book'); ?></div>
<div class="right-col-tab" data-tab="trades"><?php echo __('trades'); ?></div>
</div>
<div id="ob-panel" class="ob-panel">
<div class="ob-header">
<span><?php echo __('price'); ?>(USDT)</span>
<span><?php echo __('amount'); ?>(<span class="coin-name">--</span>)</span>
<span><?php echo __('total'); ?></span>
</div>
<div class="ob-content">
<div id="ob-panel" style="flex: 1; display: flex; flex-direction: column; overflow: hidden;">
<div class="ob-header"><span><?php echo __('price'); ?></span><span><?php echo __('amount'); ?></span></div>
<div id="ob-panel-content">
<div id="asks-list"></div>
<div id="mid-price">--</div>
<div id="mid-price" style="padding:10px 15px; text-align:center; font-weight:800; font-size:16px; border-top:1px solid #2b3139; border-bottom:1px solid #2b3139;">--</div>
<div id="bids-list"></div>
</div>
</div>
<div id="trades-panel" class="trades-panel" style="display: none;">
<div class="trades-header">
<span><?php echo __('price'); ?>(USDT)</span>
<span><?php echo __('amount'); ?>(<span class="coin-name">--</span>)</span>
<span><?php echo __('time'); ?></span>
</div>
<div id="trades-content"></div>
<div id="trades-panel" style="display:none; overflow-y:auto; flex:1;">
<div id="trades-list"></div>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
<script>
const userAssets = <?php echo json_encode($user_assets); ?>;
document.addEventListener('DOMContentLoaded', function() {
// --- Globals ---
let currentPair = 'BTCUSDT';
let currentPrice = 0;
let ws;
const marketData = {};
const pairs = [
'BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'SOLUSDT', 'XRPUSDT', 'ADAUSDT', 'DOGEUSDT', 'AVAXUSDT',
'DOTUSDT', 'MATICUSDT', 'LTCUSDT', 'TRXUSDT', 'LINKUSDT', 'UNIUSDT', 'ATOMUSDT', 'ETCUSDT',
'BCHUSDT', 'FILUSDT', 'ICPUSDT', 'NEARUSDT', 'AAVEUSDT', 'ALGOUSDT'
];
// --- DOM Elements ---
let currentPair = 'BTCUSDT', currentPrice = 0, ws, chartWidget;
const marketData = {}, pairs = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'SOLUSDT', 'XRPUSDT', 'ADAUSDT', 'DOGEUSDT', 'AVAXUSDT', 'DOTUSDT', 'MATICUSDT', 'LTCUSDT', 'TRXUSDT', 'LINKUSDT', 'UNIUSDT', 'ATOMUSDT', 'ETCUSDT', 'BCHUSDT', 'FILUSDT', 'ICPUSDT', 'NEARUSDT', 'AAVEUSDT', 'ALGOUSDT'];
const dom = {
websocketStatus: document.getElementById('websocket-status'),
currIcon: document.getElementById('curr-icon'),
currPair: document.getElementById('curr-pair'),
currPrice: document.getElementById('curr-price'),
currChange: document.getElementById('curr-change'),
hHigh: document.getElementById('h-high'),
hLow: document.getElementById('h-low'),
hVol: document.getElementById('h-vol'),
tvChartContainer: document.getElementById('tv_chart_container'),
tvContainer: document.getElementById('tv_chart_container'),
pairsList: document.getElementById('pairs-list'),
pairSearch: document.getElementById('pair-search'),
asksList: document.getElementById('asks-list'),
bidsList: document.getElementById('bids-list'),
midPrice: document.getElementById('mid-price'),
tradesContent: document.getElementById('trades-content'),
coinNameSpans: document.querySelectorAll('.coin-name'),
buyPriceInput: document.getElementById('buy-price'),
sellPriceInput: document.getElementById('sell-price'),
availUsdtSpan: document.getElementById('avail-usdt'),
availCoinSpan: document.getElementById('avail-coin')
availUsdt: document.getElementById('avail-usdt'),
availCoin: document.getElementById('avail-coin'),
coinNames: document.querySelectorAll('.coin-name'),
amountInput: document.getElementById('spot-amount'),
slider: document.getElementById('spot-slider'),
recordsTable: document.getElementById('records-table')
};
// --- Balance Management ---
function updateAvailableBalances() {
const baseCurrency = currentPair.replace('USDT', '');
const usdtBalance = parseFloat(userAssets.USDT || 0).toFixed(2);
const coinBalance = parseFloat(userAssets[baseCurrency] || 0).toFixed(6);
if (dom.availUsdtSpan) {
dom.availUsdtSpan.innerText = `${usdtBalance} USDT`;
}
if (dom.availCoinSpan) {
dom.availCoinSpan.innerText = `${coinBalance} ${baseCurrency}`;
}
function updateBalances() {
const coin = currentPair.replace('USDT', '');
dom.availUsdt.innerText = parseFloat(userAssets.USDT || 0).toFixed(2);
dom.availCoin.innerText = parseFloat(userAssets[coin] || 0).toFixed(4);
dom.coinNames.forEach(el => el.innerText = coin);
}
// --- TradingView Chart ---
function initChartWidget(symbol) {
if (dom.tvChartContainer.firstChild) {
dom.tvChartContainer.innerHTML = '';
}
const widget = new TradingView.widget({
"autosize": true,
"symbol": `BINANCE:${symbol}`,
"interval": "15",
"timezone": "Etc/UTC",
"theme": "dark",
"style": "1",
"locale": "<?php echo $lang == 'zh' ? 'zh_CN' : 'en'; ?>",
"toolbar_bg": "#f1f3f6",
"enable_publishing": false,
"withdateranges": true,
"hide_side_toolbar": true,
"allow_symbol_change": false,
"container_id": "tv_chart_container",
"studies": [
"Volume@tv-basicstudies"
],
"overrides": {
"paneProperties.background": "#161a1e",
"paneProperties.vertGridProperties.color": "rgba(255, 255, 255, 0.05)",
"paneProperties.horzGridProperties.color": "rgba(255, 255, 255, 0.05)",
"scalesProperties.textColor" : "#848e9c",
}
function initChart(symbol) {
dom.tvContainer.innerHTML = '';
new TradingView.widget({
"autosize": true, "symbol": `BINANCE:${symbol}`, "interval": "15", "theme": "dark", "style": "1",
"locale": "<?php echo $lang == 'zh' ? 'zh_CN' : 'en'; ?>", "container_id": "tv_chart_container",
"hide_side_toolbar": true, "allow_symbol_change": false, "studies": ["Volume@tv-basicstudies"],
"overrides": { "paneProperties.background": "#161a1e" }
});
}
// --- WebSocket Connection ---
function connectWebSocket() {
function connectWS() {
if (ws) ws.close();
// setWebsocketStatus('<?php echo __('websocket_connecting'); ?>', '#ffc107'); // Hidden
const streams = pairs.map(p => `${p.toLowerCase()}@ticker`).concat([`${currentPair.toLowerCase()}@depth20@100ms`, `${currentPair.toLowerCase()}@trade`]);
const streams = pairs.map(p => `${p.toLowerCase()}@ticker`).concat([`${currentPair.toLowerCase()}@depth20@100ms`]);
ws = new WebSocket(`wss://stream.binance.com:9443/stream?streams=${streams.join('/')}`);
ws.onopen = () => {
// setWebsocketStatus('<?php echo __('websocket_connected'); ?>', '#0ecb81', 2000); // Hidden
};
ws.onmessage = (event) => {
const { stream, data } = JSON.parse(event.data);
ws.onmessage = (e) => {
const { stream, data } = JSON.parse(e.data);
if (!data) return;
const type = stream.split('@')[1];
if (type === 'ticker') {
marketData[data.s] = data;
if (data.s === currentPair) {
currentPrice = parseFloat(data.c);
updatePriceUI(data);
dom.currPrice.innerText = currentPrice.toLocaleString('en-US', {minimumFractionDigits:2});
dom.currChange.innerText = (data.P >= 0 ? '+' : '') + parseFloat(data.P).toFixed(2) + '%';
dom.currPrice.style.color = dom.currChange.style.color = data.P >= 0 ? '#0ecb81' : '#f6465d';
dom.midPrice.innerText = currentPrice.toFixed(2);
}
if (dom.pairsList.offsetParent) renderPairs();
} else if (type.startsWith('depth')) {
renderOrderBook(data.bids, data.asks);
} else if (type === 'trade') {
renderTrades(data);
const row = (p, q, c) => `<div class="ob-row" onclick="document.getElementById('spot-amount').value=${parseFloat(q).toFixed(4)};"><span style="color:${c}">${parseFloat(p).toFixed(2)}</span><span>${parseFloat(q).toFixed(4)}</span></div>`;
dom.asksList.innerHTML = data.asks.slice(0, 20).reverse().map(a => row(a[0], a[1], '#f6465d')).join('');
dom.bidsList.innerHTML = data.bids.slice(0, 20).map(b => row(b[0], b[1], '#0ecb81')).join('');
}
};
ws.onerror = (error) => {
console.error('WebSocket Error:', error);
// setWebsocketStatus('<?php echo __('websocket_error'); ?>', '#f6465d'); // Hidden
};
ws.onclose = () => {
// setWebsocketStatus('<?php echo __('websocket_disconnected'); ?>', '#f6465d', 5000); // Hidden
setTimeout(connectWebSocket, 5000);
};
}
function setWebsocketStatus(message, color, hideAfter = 0) {
// This function is kept for logic continuity but does nothing visually.
return;
}
// --- UI Rendering ---
function updatePriceUI(d) {
if (!d) return;
const color = d.P >= 0 ? '#0ecb81' : '#f6465d';
const priceStr = parseFloat(d.c).toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2});
document.title = `${priceStr} | ${d.s.replace('USDT', '/USDT')}`;
dom.currPrice.innerText = priceStr;
dom.currPrice.style.color = color;
dom.currChange.innerText = `${d.P >= 0 ? '+' : ''}${parseFloat(d.P).toFixed(2)}%`;
dom.currChange.style.color = color;
dom.hHigh.innerText = parseFloat(d.h).toLocaleString('en-US', {minimumFractionDigits: 2});
dom.hLow.innerText = parseFloat(d.l).toLocaleString('en-US', {minimumFractionDigits: 2});
dom.hVol.innerText = `${(parseFloat(d.v) / 1000).toFixed(1)}K`;
dom.midPrice.innerText = priceStr;
dom.midPrice.style.color = color;
ws.onclose = () => setTimeout(connectWS, 5000);
}
function renderPairs() {
const query = dom.pairSearch.value.toUpperCase();
const filteredPairs = pairs.filter(p => p.includes(query));
dom.pairsList.innerHTML = filteredPairs.map(p => {
const q = document.getElementById('pair-search').value.toUpperCase();
dom.pairsList.innerHTML = pairs.filter(p => p.includes(q)).map(p => {
const d = marketData[p] || {};
const icon = `https://assets.coincap.io/assets/icons/${p.replace('USDT','').toLowerCase()}@2x.png`;
const price = d.c ? parseFloat(d.c).toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2}) : '--';
const change = d.P ? `${parseFloat(d.P) >= 0 ? '+' : ''}${parseFloat(d.P).toFixed(2)}%` : '--';
const color = (d.P && parseFloat(d.P) >= 0) ? '#0ecb81' : '#f6465d';
return `
<div class="pair-item ${p === currentPair ? 'active' : ''}" data-pair="${p}">
<img src="${icon}" class="coin-icon" onerror="this.style.display='none'">
<div style="flex:1"><div style="font-weight:600; font-size:13px">${p.replace('USDT', '/USDT')}</div></div>
<div style="text-align:right">
<div style="font-size:13px; font-weight:600">${price}</div>
<div style="font-size:11px; color:${color}">${change}</div>
</div>
</div>`;
return `<div class="pair-item ${p === currentPair ? 'active' : ''}" onclick="switchPair('${p}')">
<img src="https://assets.coincap.io/assets/icons/${p.replace('USDT','').toLowerCase()}@2x.png" class="coin-icon" onerror="this.style.opacity=0">
<div style="flex:1; color:white; font-weight:600">${p.replace('USDT','/USDT')}</div>
<div style="text-align:right"><div style="color:white; font-weight:600">${d.c ? parseFloat(d.c).toFixed(2) : '--'}</div><div style="color:${d.P>=0?'#0ecb81':'#f6465d'}; font-size:11px">${d.P?(d.P>=0?'+':'')+parseFloat(d.P).toFixed(2)+'%':'--'}</div></div>
</div>`;
}).join('');
}
function renderOrderBook(bids, asks) {
const renderList = (element, data, isAsks) => {
if (!element) return;
const maxRows = Math.floor(element.offsetHeight / 20);
if (maxRows <= 0) return;
window.switchPair = (p) => {
currentPair = p;
dom.currPair.innerText = p.replace('USDT', '/USDT');
dom.currIcon.src = `https://assets.coincap.io/assets/icons/${p.replace('USDT','').toLowerCase()}@2x.png`;
initChart(p);
updateBalances();
connectWS();
};
const relevantData = isAsks ? data.slice(0, maxRows).reverse() : data.slice(0, maxRows);
const maxVol = Math.max(...relevantData.map(([, q]) => parseFloat(q))) || 1;
dom.slider.oninput = () => {
const percent = dom.slider.value / 100;
const coin = currentPair.replace('USDT', '');
// Simplified slider logic
};
element.innerHTML = relevantData.map(([price, quantity]) => {
const p = parseFloat(price), q = parseFloat(quantity);
const barWidth = (q / maxVol) * 100;
const color = isAsks ? '#f6465d' : '#0ecb81';
const bgColor = isAsks ? 'rgba(246, 70, 93, 0.2)' : 'rgba(14, 203, 129, 0.2)';
return `
<div class="ob-row" onclick="setPrice(${p})">
<div class="ob-bar" style="width:${barWidth}%; background-color:${bgColor};"></div>
<span style="color:${color}; z-index:1;">${p.toFixed(2)}</span>
<span style="z-index:1;">${q.toFixed(4)}</span>
<span style="z-index:1;">${(p * q).toFixed(2)}</span>
</div>`;
}).join('');
};
renderList(dom.asksList, asks, true);
renderList(dom.bidsList, bids, false);
async function placeOrder(side) {
const amount = parseFloat(dom.amountInput.value);
if (!amount || amount <= 0) { alert("<?php echo __('amount'); ?> > 0"); return; }
const price = currentPrice;
const total = amount * price;
try {
const r = await fetch('api/place_order.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
symbol: currentPair, type: 'spot', side: side, order_type: 'market',
price: price, amount: amount, total: total
})
});
const res = await r.json();
if (res.success) { alert("<?php echo __('order_placed'); ?>"); fetchOrders(); }
else { alert(res.error || "Error"); }
} catch(e) { alert("Network Error"); }
}
function renderTrades(trade) {
if (!dom.tradesContent) return;
const color = trade.m ? '#f6465d' : '#0ecb81'; // m: is maker (seller is maker)
const time = new Date(trade.T).toLocaleTimeString('en-GB');
const row = `
<div class="trade-row">
<span style="color:${color}">${parseFloat(trade.p).toFixed(2)}</span>
<span>${parseFloat(trade.q).toFixed(4)}</span>
<span>${time}</span>
</div>`;
dom.tradesContent.insertAdjacentHTML('afterbegin', row);
if (dom.tradesContent.children.length > 50) {
dom.tradesContent.removeChild(dom.tradesContent.lastChild);
}
document.getElementById('btn-buy').onclick = () => placeOrder('buy');
document.getElementById('btn-sell').onclick = () => placeOrder('sell');
let activeTab = 'open';
async function fetchOrders() {
try {
const r = await fetch(`api/get_orders.php?type=spot&status=${activeTab}`);
const res = await r.json();
if (res.success) renderOrders(res.data);
} catch(e) {}
}
function switchPair(pair) {
currentPair = pair;
const coinName = pair.replace('USDT', '');
dom.currPair.innerText = `${coinName}/USDT`;
dom.currIcon.src = `https://assets.coincap.io/assets/icons/${coinName.toLowerCase()}@2x.png`;
dom.coinNameSpans.forEach(el => el.innerText = coinName);
updatePriceUI(marketData[pair]);
updateAvailableBalances();
initChartWidget(pair);
renderPairs();
// Clear old data and resubscribe
dom.asksList.innerHTML = '';
dom.bidsList.innerHTML = '';
dom.tradesContent.innerHTML = '';
connectWebSocket();
}
window.setPrice = (price) => {
dom.buyPriceInput.value = price;
dom.sellPriceInput.value = price;
}
// --- Event Listeners ---
dom.pairsList.addEventListener('click', (e) => {
const pairItem = e.target.closest('.pair-item');
if (pairItem && pairItem.dataset.pair) {
switchPair(pairItem.dataset.pair);
}
});
dom.pairSearch.addEventListener('input', renderPairs);
document.querySelectorAll('.right-col-tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelector('.right-col-tab.active').classList.remove('active');
tab.classList.add('active');
const isOb = tab.dataset.tab === 'ob';
document.getElementById('ob-panel').style.display = isOb ? 'flex' : 'none';
document.getElementById('trades-panel').style.display = isOb ? 'none' : 'flex';
function renderOrders(orders) {
if (!orders || orders.length === 0) { dom.recordsTable.innerHTML = '<tr><td colspan="6" style="text-align:center; padding:30px; color:#848e9c"><?php echo __('no_records'); ?></td></tr>'; return; }
let h = `<thead><tr><th><?php echo __('pair'); ?></th><th><?php echo __('direction'); ?></th><th><?php echo __('price'); ?></th><th><?php echo __('amount'); ?></th><th><?php echo __('status'); ?></th><th><?php echo __('time'); ?></th></tr></thead><tbody>`;
orders.forEach(o => {
h += `<tr><td>${o.symbol}</td><td style="color:${o.side==='buy'?'#0ecb81':'#f6465d'}">${o.side==='buy'?'<?php echo __('buy'); ?>':'<?php echo __('sell'); ?>'}</td><td>${parseFloat(o.price).toFixed(2)}</td><td>${parseFloat(o.amount).toFixed(4)}</td><td>${o.status}</td><td style="color:#848e9c">${o.created_at}</td></tr>`;
});
dom.recordsTable.innerHTML = h + '</tbody>';
}
document.querySelectorAll('.record-tab').forEach(t => t.onclick = () => {
if (!t.dataset.status) return;
document.querySelector('.record-tabs .active').classList.remove('active');
t.classList.add('active');
activeTab = t.dataset.status;
fetchOrders();
});
// --- Initial Load ---
switchPair('BTCUSDT');
fetchOrders();
setInterval(fetchOrders, 5000);
});
</script>
<!-- This script is for the TradingView widget -->
<script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
<script type="text/javascript" src="https://s3.tradingview.com/external-embedding/embed-widget-advanced-chart.js"></script>
<?php include 'footer.php'; ?>