mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-06-06 05:51:19 +08:00
feat: improve trading UI with interactive position table and chart tabs
- Add clickable position rows that scroll to chart and update symbol - Add framer-motion animations to chart tab transitions - Sync exchange selection between positions and TradingView chart - Optimize position table layout with compact styling - Update CSS with glass effects and premium button styles
This commit is contained in:
1873
web/package-lock.json
generated
1873
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
117
web/src/App.tsx
117
web/src/App.tsx
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useState, useRef } from 'react'
|
||||
import useSWR, { mutate } from 'swr'
|
||||
import { api } from './lib/api'
|
||||
import { ChartTabs } from './components/ChartTabs'
|
||||
@@ -527,6 +527,9 @@ function TraderDetailsPage({
|
||||
language: Language
|
||||
}) {
|
||||
const [closingPosition, setClosingPosition] = useState<string | null>(null)
|
||||
const [selectedChartSymbol, setSelectedChartSymbol] = useState<string | undefined>(undefined)
|
||||
const [chartUpdateKey, setChartUpdateKey] = useState<number>(0)
|
||||
const chartSectionRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
// 平仓操作
|
||||
const handleClosePosition = async (symbol: string, side: string) => {
|
||||
@@ -770,7 +773,7 @@ function TraderDetailsPage({
|
||||
>
|
||||
{getModelDisplayName(
|
||||
selectedTrader.ai_model.split('_').pop() ||
|
||||
selectedTrader.ai_model
|
||||
selectedTrader.ai_model
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
@@ -832,8 +835,17 @@ function TraderDetailsPage({
|
||||
{/* 左侧:图表 + 持仓 */}
|
||||
<div className="space-y-6">
|
||||
{/* Chart Tabs (Equity / K-line) */}
|
||||
<div className="animate-slide-in" style={{ animationDelay: '0.1s' }}>
|
||||
<ChartTabs traderId={selectedTrader.trader_id} />
|
||||
<div
|
||||
ref={chartSectionRef}
|
||||
className="chart-container animate-slide-in scroll-mt-32"
|
||||
style={{ animationDelay: '0.1s' }}
|
||||
>
|
||||
<ChartTabs
|
||||
traderId={selectedTrader.trader_id}
|
||||
selectedSymbol={selectedChartSymbol}
|
||||
updateKey={chartUpdateKey}
|
||||
exchangeId={selectedTrader.exchange_id}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Current Positions */}
|
||||
@@ -863,38 +875,38 @@ function TraderDetailsPage({
|
||||
</div>
|
||||
{positions && positions.length > 0 ? (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<table className="w-full text-xs">
|
||||
<thead className="text-left border-b border-gray-800">
|
||||
<tr>
|
||||
<th className="pb-3 font-semibold text-gray-400">
|
||||
<th className="px-1 pb-3 font-semibold text-gray-400 whitespace-nowrap text-left">
|
||||
{t('symbol', language)}
|
||||
</th>
|
||||
<th className="pb-3 font-semibold text-gray-400">
|
||||
<th className="px-1 pb-3 font-semibold text-gray-400 whitespace-nowrap text-center">
|
||||
{t('side', language)}
|
||||
</th>
|
||||
<th className="pb-3 font-semibold text-gray-400">
|
||||
<th className="px-1 pb-3 font-semibold text-gray-400 whitespace-nowrap text-center">
|
||||
{language === 'zh' ? '操作' : 'Action'}
|
||||
</th>
|
||||
<th className="pb-3 font-semibold text-gray-400">
|
||||
{t('entryPrice', language)}
|
||||
<th className="px-1 pb-3 font-semibold text-gray-400 whitespace-nowrap text-right" title={t('entryPrice', language)}>
|
||||
{language === 'zh' ? '入场价' : 'Entry'}
|
||||
</th>
|
||||
<th className="pb-3 font-semibold text-gray-400">
|
||||
{t('markPrice', language)}
|
||||
<th className="px-1 pb-3 font-semibold text-gray-400 whitespace-nowrap text-right" title={t('markPrice', language)}>
|
||||
{language === 'zh' ? '标记价' : 'Mark'}
|
||||
</th>
|
||||
<th className="pb-3 font-semibold text-gray-400">
|
||||
{t('quantity', language)}
|
||||
<th className="px-1 pb-3 font-semibold text-gray-400 whitespace-nowrap text-right" title={t('quantity', language)}>
|
||||
{language === 'zh' ? '数量' : 'Qty'}
|
||||
</th>
|
||||
<th className="pb-3 font-semibold text-gray-400">
|
||||
{t('positionValue', language)}
|
||||
<th className="px-1 pb-3 font-semibold text-gray-400 whitespace-nowrap text-right" title={t('positionValue', language)}>
|
||||
{language === 'zh' ? '价值' : 'Value'}
|
||||
</th>
|
||||
<th className="pb-3 font-semibold text-gray-400">
|
||||
{t('leverage', language)}
|
||||
<th className="px-1 pb-3 font-semibold text-gray-400 whitespace-nowrap text-center" title={t('leverage', language)}>
|
||||
{language === 'zh' ? '杠杆' : 'Lev.'}
|
||||
</th>
|
||||
<th className="pb-3 font-semibold text-gray-400">
|
||||
{t('unrealizedPnL', language)}
|
||||
<th className="px-1 pb-3 font-semibold text-gray-400 whitespace-nowrap text-right" title={t('unrealizedPnL', language)}>
|
||||
{language === 'zh' ? '未实现盈亏' : 'uPnL'}
|
||||
</th>
|
||||
<th className="pb-3 font-semibold text-gray-400">
|
||||
{t('liqPrice', language)}
|
||||
<th className="px-1 pb-3 font-semibold text-gray-400 whitespace-nowrap text-right" title={t('liqPrice', language)}>
|
||||
{language === 'zh' ? '强平价' : 'Liq.'}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -902,24 +914,32 @@ function TraderDetailsPage({
|
||||
{positions.map((pos, i) => (
|
||||
<tr
|
||||
key={i}
|
||||
className="border-b border-gray-800 last:border-0"
|
||||
className="border-b border-gray-800 last:border-0 transition-colors hover:bg-opacity-10 hover:bg-yellow-500 cursor-pointer"
|
||||
onClick={() => {
|
||||
setSelectedChartSymbol(pos.symbol)
|
||||
setChartUpdateKey(Date.now())
|
||||
// Smooth scroll to chart with ref
|
||||
if (chartSectionRef.current) {
|
||||
chartSectionRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
}
|
||||
}}
|
||||
>
|
||||
<td className="py-3 font-mono font-semibold">
|
||||
<td className="px-1 py-3 font-mono font-semibold whitespace-nowrap text-left">
|
||||
{pos.symbol}
|
||||
</td>
|
||||
<td className="py-3">
|
||||
<td className="px-1 py-3 whitespace-nowrap text-center">
|
||||
<span
|
||||
className="px-2 py-1 rounded text-xs font-bold"
|
||||
className="px-1.5 py-0.5 rounded text-[10px] font-bold"
|
||||
style={
|
||||
pos.side === 'long'
|
||||
? {
|
||||
background: 'rgba(14, 203, 129, 0.1)',
|
||||
color: '#0ECB81',
|
||||
}
|
||||
background: 'rgba(14, 203, 129, 0.1)',
|
||||
color: '#0ECB81',
|
||||
}
|
||||
: {
|
||||
background: 'rgba(246, 70, 93, 0.1)',
|
||||
color: '#F6465D',
|
||||
}
|
||||
background: 'rgba(246, 70, 93, 0.1)',
|
||||
color: '#F6465D',
|
||||
}
|
||||
}
|
||||
>
|
||||
{t(
|
||||
@@ -928,17 +948,15 @@ function TraderDetailsPage({
|
||||
)}
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-3">
|
||||
<td className="px-1 py-3 whitespace-nowrap text-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleClosePosition(pos.symbol, pos.side.toUpperCase())}
|
||||
disabled={closingPosition === pos.symbol}
|
||||
className="flex items-center gap-1 px-2 py-1 rounded text-xs font-semibold transition-all hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
style={{
|
||||
background: 'rgba(246, 70, 93, 0.1)',
|
||||
color: '#F6465D',
|
||||
border: '1px solid rgba(246, 70, 93, 0.3)',
|
||||
onClick={(e) => {
|
||||
e.stopPropagation(); // Prevent row click
|
||||
handleClosePosition(pos.symbol, pos.side.toUpperCase())
|
||||
}}
|
||||
disabled={closingPosition === pos.symbol}
|
||||
className="btn-danger inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-semibold transition-all hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed mx-auto"
|
||||
title={language === 'zh' ? '平仓' : 'Close Position'}
|
||||
>
|
||||
{closingPosition === pos.symbol ? (
|
||||
@@ -950,36 +968,36 @@ function TraderDetailsPage({
|
||||
</button>
|
||||
</td>
|
||||
<td
|
||||
className="py-3 font-mono"
|
||||
className="px-1 py-3 font-mono whitespace-nowrap text-right"
|
||||
style={{ color: '#EAECEF' }}
|
||||
>
|
||||
{pos.entry_price.toFixed(4)}
|
||||
</td>
|
||||
<td
|
||||
className="py-3 font-mono"
|
||||
className="px-1 py-3 font-mono whitespace-nowrap text-right"
|
||||
style={{ color: '#EAECEF' }}
|
||||
>
|
||||
{pos.mark_price.toFixed(4)}
|
||||
</td>
|
||||
<td
|
||||
className="py-3 font-mono"
|
||||
className="px-1 py-3 font-mono whitespace-nowrap text-right"
|
||||
style={{ color: '#EAECEF' }}
|
||||
>
|
||||
{pos.quantity.toFixed(4)}
|
||||
</td>
|
||||
<td
|
||||
className="py-3 font-mono font-bold"
|
||||
className="px-1 py-3 font-mono font-bold whitespace-nowrap text-right"
|
||||
style={{ color: '#EAECEF' }}
|
||||
>
|
||||
{(pos.quantity * pos.mark_price).toFixed(2)} USDT
|
||||
{(pos.quantity * pos.mark_price).toFixed(2)}
|
||||
</td>
|
||||
<td
|
||||
className="py-3 font-mono"
|
||||
className="px-1 py-3 font-mono whitespace-nowrap text-center"
|
||||
style={{ color: '#F0B90B' }}
|
||||
>
|
||||
{pos.leverage}x
|
||||
</td>
|
||||
<td className="py-3 font-mono">
|
||||
<td className="px-1 py-3 font-mono whitespace-nowrap text-right">
|
||||
<span
|
||||
style={{
|
||||
color:
|
||||
@@ -988,12 +1006,11 @@ function TraderDetailsPage({
|
||||
}}
|
||||
>
|
||||
{pos.unrealized_pnl >= 0 ? '+' : ''}
|
||||
{pos.unrealized_pnl.toFixed(2)} (
|
||||
{pos.unrealized_pnl_pct.toFixed(2)}%)
|
||||
{pos.unrealized_pnl.toFixed(2)}
|
||||
</span>
|
||||
</td>
|
||||
<td
|
||||
className="py-3 font-mono"
|
||||
className="px-1 py-3 font-mono whitespace-nowrap text-right"
|
||||
style={{ color: '#848E9C' }}
|
||||
>
|
||||
{pos.liquidation_price.toFixed(4)}
|
||||
|
||||
@@ -4,16 +4,18 @@ import { TradingViewChart } from './TradingViewChart'
|
||||
import { useLanguage } from '../contexts/LanguageContext'
|
||||
import { t } from '../i18n/translations'
|
||||
import { BarChart3, CandlestickChart } from 'lucide-react'
|
||||
import { motion, AnimatePresence } from 'framer-motion'
|
||||
|
||||
interface ChartTabsProps {
|
||||
traderId: string
|
||||
selectedSymbol?: string // 从外部选择的币种
|
||||
updateKey?: number // 强制更新的 key
|
||||
exchangeId?: string // 交易所ID
|
||||
}
|
||||
|
||||
type ChartTab = 'equity' | 'kline'
|
||||
|
||||
export function ChartTabs({ traderId, selectedSymbol, updateKey }: ChartTabsProps) {
|
||||
export function ChartTabs({ traderId, selectedSymbol, updateKey, exchangeId }: ChartTabsProps) {
|
||||
const { language } = useLanguage()
|
||||
const [activeTab, setActiveTab] = useState<ChartTab>('equity')
|
||||
const [chartSymbol, setChartSymbol] = useState<string>('BTCUSDT')
|
||||
@@ -44,20 +46,10 @@ export function ChartTabs({ traderId, selectedSymbol, updateKey }: ChartTabsProp
|
||||
console.log('[ChartTabs] switching to equity')
|
||||
setActiveTab('equity')
|
||||
}}
|
||||
className="flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-semibold"
|
||||
style={
|
||||
activeTab === 'equity'
|
||||
? {
|
||||
background: 'rgba(240, 185, 11, 0.15)',
|
||||
color: '#F0B90B',
|
||||
border: '1px solid rgba(240, 185, 11, 0.3)',
|
||||
}
|
||||
: {
|
||||
background: 'transparent',
|
||||
color: '#848E9C',
|
||||
border: '1px solid transparent',
|
||||
}
|
||||
}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-semibold transition-all ${activeTab === 'equity'
|
||||
? 'bg-yellow-500/10 text-yellow-500 border border-yellow-500/30 shadow-[0_0_10px_rgba(252,213,53,0.15)]'
|
||||
: 'text-gray-400 hover:text-white hover:bg-white/5 border border-transparent'
|
||||
}`}
|
||||
>
|
||||
<BarChart3 className="w-4 h-4" />
|
||||
{t('accountEquityCurve', language)}
|
||||
@@ -68,20 +60,10 @@ export function ChartTabs({ traderId, selectedSymbol, updateKey }: ChartTabsProp
|
||||
console.log('[ChartTabs] switching to kline')
|
||||
setActiveTab('kline')
|
||||
}}
|
||||
className="flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-semibold"
|
||||
style={
|
||||
activeTab === 'kline'
|
||||
? {
|
||||
background: 'rgba(240, 185, 11, 0.15)',
|
||||
color: '#F0B90B',
|
||||
border: '1px solid rgba(240, 185, 11, 0.3)',
|
||||
}
|
||||
: {
|
||||
background: 'transparent',
|
||||
color: '#848E9C',
|
||||
border: '1px solid transparent',
|
||||
}
|
||||
}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-semibold transition-all ${activeTab === 'kline'
|
||||
? 'bg-yellow-500/10 text-yellow-500 border border-yellow-500/30 shadow-[0_0_10px_rgba(252,213,53,0.15)]'
|
||||
: 'text-gray-400 hover:text-white hover:bg-white/5 border border-transparent'
|
||||
}`}
|
||||
>
|
||||
<CandlestickChart className="w-4 h-4" />
|
||||
{t('marketChart', language)}
|
||||
@@ -89,17 +71,38 @@ export function ChartTabs({ traderId, selectedSymbol, updateKey }: ChartTabsProp
|
||||
</div>
|
||||
|
||||
{/* Tab Content */}
|
||||
<div>
|
||||
{activeTab === 'equity' ? (
|
||||
<EquityChart traderId={traderId} embedded />
|
||||
) : (
|
||||
<TradingViewChart
|
||||
height={400}
|
||||
embedded
|
||||
defaultSymbol={chartSymbol}
|
||||
key={chartSymbol} // 强制重新渲染当币种变化时
|
||||
/>
|
||||
)}
|
||||
<div className="relative overflow-hidden min-h-[400px]">
|
||||
<AnimatePresence mode="wait">
|
||||
{activeTab === 'equity' ? (
|
||||
<motion.div
|
||||
key="equity"
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: 20 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="h-full"
|
||||
>
|
||||
<EquityChart traderId={traderId} embedded />
|
||||
</motion.div>
|
||||
) : (
|
||||
<motion.div
|
||||
key={`kline-${chartSymbol}-${exchangeId}`}
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: -20 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="h-full"
|
||||
>
|
||||
<TradingViewChart
|
||||
height={400}
|
||||
embedded
|
||||
defaultSymbol={chartSymbol}
|
||||
defaultExchange={exchangeId}
|
||||
key={`${chartSymbol}-${exchangeId}-${updateKey || ''}`}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -69,11 +69,22 @@ function TradingViewChartComponent({
|
||||
// 当外部传入的 defaultSymbol 变化时,更新内部 symbol
|
||||
useEffect(() => {
|
||||
if (defaultSymbol && defaultSymbol !== symbol) {
|
||||
console.log('[TradingViewChart] 更新币种:', defaultSymbol)
|
||||
// console.log('[TradingViewChart] 更新币种:', defaultSymbol)
|
||||
setSymbol(defaultSymbol)
|
||||
}
|
||||
}, [defaultSymbol])
|
||||
|
||||
// 当外部传入的 defaultExchange 变化时,更新内部 exchange
|
||||
useEffect(() => {
|
||||
if (defaultExchange && defaultExchange !== exchange) {
|
||||
const normalizedExchange = defaultExchange.toUpperCase()
|
||||
// console.log('[TradingViewChart] 更新交易所:', normalizedExchange)
|
||||
if (EXCHANGES.some(e => e.id === normalizedExchange)) {
|
||||
setExchange(normalizedExchange)
|
||||
}
|
||||
}
|
||||
}, [defaultExchange])
|
||||
|
||||
// 获取完整的交易对符号 (合约格式: BINANCE:BTCUSDT.P)
|
||||
const getFullSymbol = () => {
|
||||
const exchangeInfo = EXCHANGES.find((e) => e.id === exchange)
|
||||
@@ -154,11 +165,10 @@ function TradingViewChartComponent({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${embedded ? '' : 'binance-card'} overflow-hidden ${embedded ? '' : 'animate-fade-in'} ${
|
||||
isFullscreen
|
||||
className={`${embedded ? '' : 'binance-card'} overflow-hidden ${embedded ? '' : 'animate-fade-in'} ${isFullscreen
|
||||
? 'fixed inset-0 z-50 rounded-none flex flex-col'
|
||||
: ''
|
||||
}`}
|
||||
}`}
|
||||
style={isFullscreen ? { background: '#0B0E11' } : undefined}
|
||||
>
|
||||
{/* Header */}
|
||||
|
||||
@@ -12,55 +12,48 @@ html {
|
||||
|
||||
:root {
|
||||
/* Binance Brand Colors */
|
||||
--brand-yellow: #f0b90b;
|
||||
--brand-black: #000000;
|
||||
--brand-dark-gray: #0a0a0a;
|
||||
--brand-light-gray: #eaecef;
|
||||
--brand-almost-white: #fafafa;
|
||||
--brand-white: #ffffff;
|
||||
--brand-yellow: #FCD535;
|
||||
--brand-black: #0B0E11;
|
||||
--brand-dark-gray: #1E2329;
|
||||
--brand-light-gray: #EAECEF;
|
||||
--brand-white: #FFFFFF;
|
||||
|
||||
/* Binance Theme Colors */
|
||||
--binance-yellow: #f0b90b;
|
||||
--binance-yellow-dark: #c99400;
|
||||
--binance-yellow-light: #fcd535;
|
||||
--binance-yellow-glow: rgba(240, 185, 11, 0.2);
|
||||
/* Premium Theme Colors */
|
||||
--binance-yellow: #FCD535;
|
||||
--binance-yellow-dark: #F0B90B;
|
||||
--binance-yellow-light: #FDE059;
|
||||
--binance-yellow-glow: rgba(252, 213, 53, 0.15);
|
||||
|
||||
--background: #000000; /* Binance body bg */
|
||||
--header-bg: #000000; /* Binance header bg */
|
||||
--background-elevated: #000000;
|
||||
--foreground: #eaecef;
|
||||
--panel-bg: #0a0a0a;
|
||||
--panel-bg-hover: #111111;
|
||||
--panel-border: #1a1a1a;
|
||||
--panel-border-hover: #2a2a2a;
|
||||
--background: #0B0E11;
|
||||
--header-bg: rgba(11, 14, 17, 0.85);
|
||||
/* Glass header */
|
||||
--glass-bg: rgba(30, 35, 41, 0.4);
|
||||
--glass-border: rgba(255, 255, 255, 0.08);
|
||||
|
||||
/* Binance Signature Colors */
|
||||
--binance-green: #0ecb81;
|
||||
--binance-green-bg: rgba(14, 203, 129, 0.1);
|
||||
--binance-green-border: rgba(14, 203, 129, 0.2);
|
||||
--binance-red: #f6465d;
|
||||
--binance-red-bg: rgba(246, 70, 93, 0.1);
|
||||
--binance-red-border: rgba(246, 70, 93, 0.2);
|
||||
--panel-bg: #1E2329;
|
||||
--panel-bg-hover: #2B3139;
|
||||
--panel-border: #2B3139;
|
||||
--panel-border-hover: #474D57;
|
||||
|
||||
/* UI Colors */
|
||||
--text-primary: #eaecef;
|
||||
--text-secondary: #848e9c;
|
||||
--text-tertiary: #5e6673;
|
||||
--text-disabled: #474d57;
|
||||
--foreground: #EAECEF;
|
||||
--text-primary: #EAECEF;
|
||||
--text-secondary: #848E9C;
|
||||
--text-tertiary: #5E6673;
|
||||
--text-disabled: #474D57;
|
||||
|
||||
/* Chart Colors */
|
||||
--grid-stroke: #1a1a1a;
|
||||
--axis-tick: #5e6673;
|
||||
--ref-line: #474d57;
|
||||
/* Trading Colors */
|
||||
--binance-green: #0ECB81;
|
||||
--binance-green-bg: rgba(14, 203, 129, 0.12);
|
||||
--binance-green-border: rgba(14, 203, 129, 0.3);
|
||||
--binance-red: #F6465D;
|
||||
--binance-red-bg: rgba(246, 70, 93, 0.12);
|
||||
--binance-red-border: rgba(246, 70, 93, 0.3);
|
||||
|
||||
/* Shadows */
|
||||
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3);
|
||||
--shadow-md:
|
||||
0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
|
||||
--shadow-lg:
|
||||
0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -2px rgba(0, 0, 0, 0.3);
|
||||
--shadow-xl:
|
||||
0 20px 25px -5px rgba(0, 0, 0, 0.6), 0 10px 10px -5px rgba(0, 0, 0, 0.4);
|
||||
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
--shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.4);
|
||||
--shadow-glow: 0 0 20px rgba(252, 213, 53, 0.1);
|
||||
|
||||
font-family:
|
||||
'Inter',
|
||||
@@ -86,6 +79,16 @@ body {
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
background-color: var(--background);
|
||||
background-image:
|
||||
radial-gradient(circle at 15% 50%, rgba(252, 213, 53, 0.08), transparent 25%),
|
||||
radial-gradient(circle at 85% 30%, rgba(14, 203, 129, 0.05), transparent 25%);
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
/* Premium Selection Styles */
|
||||
::selection {
|
||||
background: rgba(252, 213, 53, 0.3);
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
#root {
|
||||
@@ -119,6 +122,7 @@ body {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
@@ -130,6 +134,7 @@ body {
|
||||
opacity: 0;
|
||||
transform: translateX(-20px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
@@ -141,6 +146,7 @@ body {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
@@ -148,11 +154,13 @@ body {
|
||||
}
|
||||
|
||||
@keyframes pulse-glow {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
box-shadow: 0 0 8px currentColor;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
box-shadow: 0 0 16px currentColor;
|
||||
@@ -163,16 +171,19 @@ body {
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-scale {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
@@ -197,7 +208,9 @@ body {
|
||||
/* Glass effect - Binance header style */
|
||||
.header-bar {
|
||||
background: var(--header-bg);
|
||||
border-bottom: 1px solid var(--panel-border);
|
||||
border-bottom: 1px solid var(--glass-border);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
}
|
||||
|
||||
/* Sonner (toast) - Binance theme overrides */
|
||||
@@ -228,6 +241,7 @@ body {
|
||||
border-color: var(--binance-green) !important;
|
||||
border-left: 3px solid var(--binance-green) !important;
|
||||
}
|
||||
|
||||
.nofx-toast[data-type='success'] .sonner-title,
|
||||
.nofx-toast[data-type='success'] .sonner-description {
|
||||
color: var(--binance-green) !important;
|
||||
@@ -238,6 +252,7 @@ body {
|
||||
border-color: var(--binance-red) !important;
|
||||
border-left: 3px solid var(--binance-red) !important;
|
||||
}
|
||||
|
||||
.nofx-toast[data-type='error'] .sonner-title,
|
||||
.nofx-toast[data-type='error'] .sonner-description {
|
||||
color: var(--binance-red) !important;
|
||||
@@ -249,6 +264,7 @@ body {
|
||||
border-color: var(--binance-yellow) !important;
|
||||
border-left: 3px solid var(--binance-yellow) !important;
|
||||
}
|
||||
|
||||
.nofx-toast[data-type='warning'] .sonner-title,
|
||||
.nofx-toast[data-type='warning'] .sonner-description,
|
||||
.nofx-toast[data-type='info'] .sonner-title,
|
||||
@@ -259,6 +275,7 @@ body {
|
||||
.nofx-toast .sonner-close-button {
|
||||
color: var(--text-secondary) !important;
|
||||
}
|
||||
|
||||
.nofx-toast .sonner-close-button:hover {
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
@@ -276,12 +293,24 @@ button {
|
||||
}
|
||||
|
||||
button:hover:not(:disabled) {
|
||||
filter: brightness(1.1);
|
||||
filter: brightness(1.1);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
button:active:not(:disabled) {
|
||||
transform: scale(0.98) translateY(0);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Soft Glow for Primary Buttons (Yellow) */
|
||||
.btn-primary-glow {
|
||||
box-shadow: 0 0 15px rgba(252, 213, 53, 0.15);
|
||||
}
|
||||
|
||||
.btn-primary-glow:hover {
|
||||
box-shadow: 0 0 20px rgba(252, 213, 53, 0.3);
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
@@ -289,13 +318,85 @@ button:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Binance Card - Enhanced */
|
||||
/* Button Variants */
|
||||
.btn-success {
|
||||
background: rgba(14, 203, 129, 0.1);
|
||||
color: #0ECB81;
|
||||
border: 1px solid rgba(14, 203, 129, 0.3);
|
||||
}
|
||||
|
||||
.btn-success:hover:not(:disabled) {
|
||||
background: rgba(14, 203, 129, 0.2);
|
||||
box-shadow: 0 0 15px rgba(14, 203, 129, 0.2);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: rgba(246, 70, 93, 0.1);
|
||||
color: #F6465D;
|
||||
border: 1px solid rgba(246, 70, 93, 0.3);
|
||||
}
|
||||
|
||||
.btn-danger:hover:not(:disabled) {
|
||||
background: rgba(246, 70, 93, 0.2);
|
||||
box-shadow: 0 0 15px rgba(246, 70, 93, 0.2);
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
color: #848E9C;
|
||||
border: 1px solid #2B3139;
|
||||
}
|
||||
|
||||
.btn-outline:hover:not(:disabled) {
|
||||
border-color: #F0B90B;
|
||||
color: #F0B90B;
|
||||
background: rgba(240, 185, 11, 0.1);
|
||||
}
|
||||
|
||||
/* Glass Effect Utility */
|
||||
.glass {
|
||||
background: rgba(30, 35, 41, 0.6);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.glass-panel {
|
||||
background: rgba(30, 35, 41, 0.4);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* Premium Input Styles */
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
input:focus,
|
||||
select:focus,
|
||||
textarea:focus {
|
||||
border-color: var(--binance-yellow);
|
||||
box-shadow: 0 0 0 2px rgba(252, 213, 53, 0.15);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Binance Card - Premium Polish */
|
||||
.binance-card {
|
||||
background: var(--panel-bg);
|
||||
background: rgba(30, 35, 41, 0.4);
|
||||
/* More transparent for glass feel */
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid var(--panel-border);
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: var(--shadow-sm);
|
||||
border-radius: 12px;
|
||||
/* More modern radius */
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
overflow: hidden;
|
||||
/* Ensure rounded corners contain content */
|
||||
}
|
||||
|
||||
.dev-toast-controller {
|
||||
@@ -409,6 +510,7 @@ button:disabled {
|
||||
border-color: var(--panel-border-hover);
|
||||
box-shadow: var(--shadow-md);
|
||||
transform: translateY(-2px);
|
||||
background: var(--panel-bg-hover);
|
||||
}
|
||||
|
||||
.binance-card-no-hover {
|
||||
@@ -420,19 +522,15 @@ button:disabled {
|
||||
|
||||
/* Binance gradient backgrounds */
|
||||
.binance-gradient {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--binance-yellow) 0%,
|
||||
var(--binance-yellow-light) 100%
|
||||
);
|
||||
background: linear-gradient(135deg,
|
||||
var(--binance-yellow) 0%,
|
||||
var(--binance-yellow-light) 100%);
|
||||
}
|
||||
|
||||
.binance-gradient-subtle {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(240, 185, 11, 0.15) 0%,
|
||||
rgba(252, 213, 53, 0.05) 100%
|
||||
);
|
||||
background: linear-gradient(135deg,
|
||||
rgba(240, 185, 11, 0.15) 0%,
|
||||
rgba(252, 213, 53, 0.05) 100%);
|
||||
border: 1px solid rgba(240, 185, 11, 0.2);
|
||||
}
|
||||
|
||||
@@ -466,12 +564,10 @@ button:disabled {
|
||||
/* Skeleton loading */
|
||||
.skeleton {
|
||||
background: var(--panel-bg);
|
||||
background-image: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
rgba(240, 185, 11, 0.05),
|
||||
transparent
|
||||
);
|
||||
background-image: linear-gradient(90deg,
|
||||
transparent,
|
||||
rgba(240, 185, 11, 0.05),
|
||||
transparent);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.5s infinite;
|
||||
border-radius: 4px;
|
||||
@@ -644,12 +740,10 @@ tr:hover {
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
var(--binance-yellow),
|
||||
transparent
|
||||
);
|
||||
background: linear-gradient(90deg,
|
||||
transparent,
|
||||
var(--binance-yellow),
|
||||
transparent);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
@@ -682,4 +776,4 @@ tr:hover {
|
||||
.stat-card {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user