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:
tinkle-community
2025-12-08 00:34:49 +08:00
parent 2334d78e4a
commit 0636ced476
5 changed files with 1204 additions and 1123 deletions

1873
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react' import { useEffect, useState, useRef } from 'react'
import useSWR, { mutate } from 'swr' import useSWR, { mutate } from 'swr'
import { api } from './lib/api' import { api } from './lib/api'
import { ChartTabs } from './components/ChartTabs' import { ChartTabs } from './components/ChartTabs'
@@ -527,6 +527,9 @@ function TraderDetailsPage({
language: Language language: Language
}) { }) {
const [closingPosition, setClosingPosition] = useState<string | null>(null) 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) => { const handleClosePosition = async (symbol: string, side: string) => {
@@ -770,7 +773,7 @@ function TraderDetailsPage({
> >
{getModelDisplayName( {getModelDisplayName(
selectedTrader.ai_model.split('_').pop() || selectedTrader.ai_model.split('_').pop() ||
selectedTrader.ai_model selectedTrader.ai_model
)} )}
</span> </span>
</span> </span>
@@ -832,8 +835,17 @@ function TraderDetailsPage({
{/* 左侧:图表 + 持仓 */} {/* 左侧:图表 + 持仓 */}
<div className="space-y-6"> <div className="space-y-6">
{/* Chart Tabs (Equity / K-line) */} {/* Chart Tabs (Equity / K-line) */}
<div className="animate-slide-in" style={{ animationDelay: '0.1s' }}> <div
<ChartTabs traderId={selectedTrader.trader_id} /> 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> </div>
{/* Current Positions */} {/* Current Positions */}
@@ -863,38 +875,38 @@ function TraderDetailsPage({
</div> </div>
{positions && positions.length > 0 ? ( {positions && positions.length > 0 ? (
<div className="overflow-x-auto"> <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"> <thead className="text-left border-b border-gray-800">
<tr> <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)} {t('symbol', language)}
</th> </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)} {t('side', language)}
</th> </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'} {language === 'zh' ? '操作' : 'Action'}
</th> </th>
<th className="pb-3 font-semibold text-gray-400"> <th className="px-1 pb-3 font-semibold text-gray-400 whitespace-nowrap text-right" title={t('entryPrice', language)}>
{t('entryPrice', language)} {language === 'zh' ? '入场价' : 'Entry'}
</th> </th>
<th className="pb-3 font-semibold text-gray-400"> <th className="px-1 pb-3 font-semibold text-gray-400 whitespace-nowrap text-right" title={t('markPrice', language)}>
{t('markPrice', language)} {language === 'zh' ? '标记价' : 'Mark'}
</th> </th>
<th className="pb-3 font-semibold text-gray-400"> <th className="px-1 pb-3 font-semibold text-gray-400 whitespace-nowrap text-right" title={t('quantity', language)}>
{t('quantity', language)} {language === 'zh' ? '数量' : 'Qty'}
</th> </th>
<th className="pb-3 font-semibold text-gray-400"> <th className="px-1 pb-3 font-semibold text-gray-400 whitespace-nowrap text-right" title={t('positionValue', language)}>
{t('positionValue', language)} {language === 'zh' ? '价值' : 'Value'}
</th> </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" title={t('leverage', language)}>
{t('leverage', language)} {language === 'zh' ? '杠杆' : 'Lev.'}
</th> </th>
<th className="pb-3 font-semibold text-gray-400"> <th className="px-1 pb-3 font-semibold text-gray-400 whitespace-nowrap text-right" title={t('unrealizedPnL', language)}>
{t('unrealizedPnL', language)} {language === 'zh' ? '未实现盈亏' : 'uPnL'}
</th> </th>
<th className="pb-3 font-semibold text-gray-400"> <th className="px-1 pb-3 font-semibold text-gray-400 whitespace-nowrap text-right" title={t('liqPrice', language)}>
{t('liqPrice', language)} {language === 'zh' ? '强平价' : 'Liq.'}
</th> </th>
</tr> </tr>
</thead> </thead>
@@ -902,24 +914,32 @@ function TraderDetailsPage({
{positions.map((pos, i) => ( {positions.map((pos, i) => (
<tr <tr
key={i} 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} {pos.symbol}
</td> </td>
<td className="py-3"> <td className="px-1 py-3 whitespace-nowrap text-center">
<span <span
className="px-2 py-1 rounded text-xs font-bold" className="px-1.5 py-0.5 rounded text-[10px] font-bold"
style={ style={
pos.side === 'long' pos.side === 'long'
? { ? {
background: 'rgba(14, 203, 129, 0.1)', background: 'rgba(14, 203, 129, 0.1)',
color: '#0ECB81', color: '#0ECB81',
} }
: { : {
background: 'rgba(246, 70, 93, 0.1)', background: 'rgba(246, 70, 93, 0.1)',
color: '#F6465D', color: '#F6465D',
} }
} }
> >
{t( {t(
@@ -928,17 +948,15 @@ function TraderDetailsPage({
)} )}
</span> </span>
</td> </td>
<td className="py-3"> <td className="px-1 py-3 whitespace-nowrap text-center">
<button <button
type="button" type="button"
onClick={() => handleClosePosition(pos.symbol, pos.side.toUpperCase())} onClick={(e) => {
disabled={closingPosition === pos.symbol} e.stopPropagation(); // Prevent row click
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" handleClosePosition(pos.symbol, pos.side.toUpperCase())
style={{
background: 'rgba(246, 70, 93, 0.1)',
color: '#F6465D',
border: '1px solid rgba(246, 70, 93, 0.3)',
}} }}
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'} title={language === 'zh' ? '平仓' : 'Close Position'}
> >
{closingPosition === pos.symbol ? ( {closingPosition === pos.symbol ? (
@@ -950,36 +968,36 @@ function TraderDetailsPage({
</button> </button>
</td> </td>
<td <td
className="py-3 font-mono" className="px-1 py-3 font-mono whitespace-nowrap text-right"
style={{ color: '#EAECEF' }} style={{ color: '#EAECEF' }}
> >
{pos.entry_price.toFixed(4)} {pos.entry_price.toFixed(4)}
</td> </td>
<td <td
className="py-3 font-mono" className="px-1 py-3 font-mono whitespace-nowrap text-right"
style={{ color: '#EAECEF' }} style={{ color: '#EAECEF' }}
> >
{pos.mark_price.toFixed(4)} {pos.mark_price.toFixed(4)}
</td> </td>
<td <td
className="py-3 font-mono" className="px-1 py-3 font-mono whitespace-nowrap text-right"
style={{ color: '#EAECEF' }} style={{ color: '#EAECEF' }}
> >
{pos.quantity.toFixed(4)} {pos.quantity.toFixed(4)}
</td> </td>
<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' }} style={{ color: '#EAECEF' }}
> >
{(pos.quantity * pos.mark_price).toFixed(2)} USDT {(pos.quantity * pos.mark_price).toFixed(2)}
</td> </td>
<td <td
className="py-3 font-mono" className="px-1 py-3 font-mono whitespace-nowrap text-center"
style={{ color: '#F0B90B' }} style={{ color: '#F0B90B' }}
> >
{pos.leverage}x {pos.leverage}x
</td> </td>
<td className="py-3 font-mono"> <td className="px-1 py-3 font-mono whitespace-nowrap text-right">
<span <span
style={{ style={{
color: color:
@@ -988,12 +1006,11 @@ function TraderDetailsPage({
}} }}
> >
{pos.unrealized_pnl >= 0 ? '+' : ''} {pos.unrealized_pnl >= 0 ? '+' : ''}
{pos.unrealized_pnl.toFixed(2)} ( {pos.unrealized_pnl.toFixed(2)}
{pos.unrealized_pnl_pct.toFixed(2)}%)
</span> </span>
</td> </td>
<td <td
className="py-3 font-mono" className="px-1 py-3 font-mono whitespace-nowrap text-right"
style={{ color: '#848E9C' }} style={{ color: '#848E9C' }}
> >
{pos.liquidation_price.toFixed(4)} {pos.liquidation_price.toFixed(4)}

View File

@@ -4,16 +4,18 @@ import { TradingViewChart } from './TradingViewChart'
import { useLanguage } from '../contexts/LanguageContext' import { useLanguage } from '../contexts/LanguageContext'
import { t } from '../i18n/translations' import { t } from '../i18n/translations'
import { BarChart3, CandlestickChart } from 'lucide-react' import { BarChart3, CandlestickChart } from 'lucide-react'
import { motion, AnimatePresence } from 'framer-motion'
interface ChartTabsProps { interface ChartTabsProps {
traderId: string traderId: string
selectedSymbol?: string // 从外部选择的币种 selectedSymbol?: string // 从外部选择的币种
updateKey?: number // 强制更新的 key updateKey?: number // 强制更新的 key
exchangeId?: string // 交易所ID
} }
type ChartTab = 'equity' | 'kline' type ChartTab = 'equity' | 'kline'
export function ChartTabs({ traderId, selectedSymbol, updateKey }: ChartTabsProps) { export function ChartTabs({ traderId, selectedSymbol, updateKey, exchangeId }: ChartTabsProps) {
const { language } = useLanguage() const { language } = useLanguage()
const [activeTab, setActiveTab] = useState<ChartTab>('equity') const [activeTab, setActiveTab] = useState<ChartTab>('equity')
const [chartSymbol, setChartSymbol] = useState<string>('BTCUSDT') const [chartSymbol, setChartSymbol] = useState<string>('BTCUSDT')
@@ -44,20 +46,10 @@ export function ChartTabs({ traderId, selectedSymbol, updateKey }: ChartTabsProp
console.log('[ChartTabs] switching to equity') console.log('[ChartTabs] switching to equity')
setActiveTab('equity') setActiveTab('equity')
}} }}
className="flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-semibold" className={`flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-semibold transition-all ${activeTab === 'equity'
style={ ? 'bg-yellow-500/10 text-yellow-500 border border-yellow-500/30 shadow-[0_0_10px_rgba(252,213,53,0.15)]'
activeTab === 'equity' : 'text-gray-400 hover:text-white hover:bg-white/5 border border-transparent'
? { }`}
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',
}
}
> >
<BarChart3 className="w-4 h-4" /> <BarChart3 className="w-4 h-4" />
{t('accountEquityCurve', language)} {t('accountEquityCurve', language)}
@@ -68,20 +60,10 @@ export function ChartTabs({ traderId, selectedSymbol, updateKey }: ChartTabsProp
console.log('[ChartTabs] switching to kline') console.log('[ChartTabs] switching to kline')
setActiveTab('kline') setActiveTab('kline')
}} }}
className="flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-semibold" className={`flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-semibold transition-all ${activeTab === 'kline'
style={ ? 'bg-yellow-500/10 text-yellow-500 border border-yellow-500/30 shadow-[0_0_10px_rgba(252,213,53,0.15)]'
activeTab === 'kline' : 'text-gray-400 hover:text-white hover:bg-white/5 border border-transparent'
? { }`}
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',
}
}
> >
<CandlestickChart className="w-4 h-4" /> <CandlestickChart className="w-4 h-4" />
{t('marketChart', language)} {t('marketChart', language)}
@@ -89,17 +71,38 @@ export function ChartTabs({ traderId, selectedSymbol, updateKey }: ChartTabsProp
</div> </div>
{/* Tab Content */} {/* Tab Content */}
<div> <div className="relative overflow-hidden min-h-[400px]">
{activeTab === 'equity' ? ( <AnimatePresence mode="wait">
<EquityChart traderId={traderId} embedded /> {activeTab === 'equity' ? (
) : ( <motion.div
<TradingViewChart key="equity"
height={400} initial={{ opacity: 0, x: -20 }}
embedded animate={{ opacity: 1, x: 0 }}
defaultSymbol={chartSymbol} exit={{ opacity: 0, x: 20 }}
key={chartSymbol} // 强制重新渲染当币种变化时 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>
</div> </div>
) )

View File

@@ -69,11 +69,22 @@ function TradingViewChartComponent({
// 当外部传入的 defaultSymbol 变化时,更新内部 symbol // 当外部传入的 defaultSymbol 变化时,更新内部 symbol
useEffect(() => { useEffect(() => {
if (defaultSymbol && defaultSymbol !== symbol) { if (defaultSymbol && defaultSymbol !== symbol) {
console.log('[TradingViewChart] 更新币种:', defaultSymbol) // console.log('[TradingViewChart] 更新币种:', defaultSymbol)
setSymbol(defaultSymbol) setSymbol(defaultSymbol)
} }
}, [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) // 获取完整的交易对符号 (合约格式: BINANCE:BTCUSDT.P)
const getFullSymbol = () => { const getFullSymbol = () => {
const exchangeInfo = EXCHANGES.find((e) => e.id === exchange) const exchangeInfo = EXCHANGES.find((e) => e.id === exchange)
@@ -154,11 +165,10 @@ function TradingViewChartComponent({
return ( return (
<div <div
className={`${embedded ? '' : 'binance-card'} overflow-hidden ${embedded ? '' : 'animate-fade-in'} ${ className={`${embedded ? '' : 'binance-card'} overflow-hidden ${embedded ? '' : 'animate-fade-in'} ${isFullscreen
isFullscreen
? 'fixed inset-0 z-50 rounded-none flex flex-col' ? 'fixed inset-0 z-50 rounded-none flex flex-col'
: '' : ''
}`} }`}
style={isFullscreen ? { background: '#0B0E11' } : undefined} style={isFullscreen ? { background: '#0B0E11' } : undefined}
> >
{/* Header */} {/* Header */}

View File

@@ -12,55 +12,48 @@ html {
:root { :root {
/* Binance Brand Colors */ /* Binance Brand Colors */
--brand-yellow: #f0b90b; --brand-yellow: #FCD535;
--brand-black: #000000; --brand-black: #0B0E11;
--brand-dark-gray: #0a0a0a; --brand-dark-gray: #1E2329;
--brand-light-gray: #eaecef; --brand-light-gray: #EAECEF;
--brand-almost-white: #fafafa; --brand-white: #FFFFFF;
--brand-white: #ffffff;
/* Binance Theme Colors */ /* Premium Theme Colors */
--binance-yellow: #f0b90b; --binance-yellow: #FCD535;
--binance-yellow-dark: #c99400; --binance-yellow-dark: #F0B90B;
--binance-yellow-light: #fcd535; --binance-yellow-light: #FDE059;
--binance-yellow-glow: rgba(240, 185, 11, 0.2); --binance-yellow-glow: rgba(252, 213, 53, 0.15);
--background: #000000; /* Binance body bg */ --background: #0B0E11;
--header-bg: #000000; /* Binance header bg */ --header-bg: rgba(11, 14, 17, 0.85);
--background-elevated: #000000; /* Glass header */
--foreground: #eaecef; --glass-bg: rgba(30, 35, 41, 0.4);
--panel-bg: #0a0a0a; --glass-border: rgba(255, 255, 255, 0.08);
--panel-bg-hover: #111111;
--panel-border: #1a1a1a;
--panel-border-hover: #2a2a2a;
/* Binance Signature Colors */ --panel-bg: #1E2329;
--binance-green: #0ecb81; --panel-bg-hover: #2B3139;
--binance-green-bg: rgba(14, 203, 129, 0.1); --panel-border: #2B3139;
--binance-green-border: rgba(14, 203, 129, 0.2); --panel-border-hover: #474D57;
--binance-red: #f6465d;
--binance-red-bg: rgba(246, 70, 93, 0.1);
--binance-red-border: rgba(246, 70, 93, 0.2);
/* UI Colors */ --foreground: #EAECEF;
--text-primary: #eaecef; --text-primary: #EAECEF;
--text-secondary: #848e9c; --text-secondary: #848E9C;
--text-tertiary: #5e6673; --text-tertiary: #5E6673;
--text-disabled: #474d57; --text-disabled: #474D57;
/* Chart Colors */ /* Trading Colors */
--grid-stroke: #1a1a1a; --binance-green: #0ECB81;
--axis-tick: #5e6673; --binance-green-bg: rgba(14, 203, 129, 0.12);
--ref-line: #474d57; --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 */ /* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3); --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.2);
--shadow-md: --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.3);
0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.3); --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.4);
--shadow-lg: --shadow-glow: 0 0 20px rgba(252, 213, 53, 0.1);
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);
font-family: font-family:
'Inter', 'Inter',
@@ -86,6 +79,16 @@ body {
min-width: 320px; min-width: 320px;
min-height: 100vh; min-height: 100vh;
background-color: var(--background); 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 { #root {
@@ -119,6 +122,7 @@ body {
opacity: 0; opacity: 0;
transform: translateY(10px); transform: translateY(10px);
} }
to { to {
opacity: 1; opacity: 1;
transform: translateY(0); transform: translateY(0);
@@ -130,6 +134,7 @@ body {
opacity: 0; opacity: 0;
transform: translateX(-20px); transform: translateX(-20px);
} }
to { to {
opacity: 1; opacity: 1;
transform: translateX(0); transform: translateX(0);
@@ -141,6 +146,7 @@ body {
opacity: 0; opacity: 0;
transform: scale(0.95); transform: scale(0.95);
} }
to { to {
opacity: 1; opacity: 1;
transform: scale(1); transform: scale(1);
@@ -148,11 +154,13 @@ body {
} }
@keyframes pulse-glow { @keyframes pulse-glow {
0%, 0%,
100% { 100% {
opacity: 1; opacity: 1;
box-shadow: 0 0 8px currentColor; box-shadow: 0 0 8px currentColor;
} }
50% { 50% {
opacity: 0.7; opacity: 0.7;
box-shadow: 0 0 16px currentColor; box-shadow: 0 0 16px currentColor;
@@ -163,16 +171,19 @@ body {
0% { 0% {
transform: translateX(-100%); transform: translateX(-100%);
} }
100% { 100% {
transform: translateX(100%); transform: translateX(100%);
} }
} }
@keyframes pulse-scale { @keyframes pulse-scale {
0%, 0%,
100% { 100% {
transform: scale(1); transform: scale(1);
} }
50% { 50% {
transform: scale(1.05); transform: scale(1.05);
} }
@@ -197,7 +208,9 @@ body {
/* Glass effect - Binance header style */ /* Glass effect - Binance header style */
.header-bar { .header-bar {
background: var(--header-bg); 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 */ /* Sonner (toast) - Binance theme overrides */
@@ -228,6 +241,7 @@ body {
border-color: var(--binance-green) !important; border-color: var(--binance-green) !important;
border-left: 3px solid 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-title,
.nofx-toast[data-type='success'] .sonner-description { .nofx-toast[data-type='success'] .sonner-description {
color: var(--binance-green) !important; color: var(--binance-green) !important;
@@ -238,6 +252,7 @@ body {
border-color: var(--binance-red) !important; border-color: var(--binance-red) !important;
border-left: 3px solid 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-title,
.nofx-toast[data-type='error'] .sonner-description { .nofx-toast[data-type='error'] .sonner-description {
color: var(--binance-red) !important; color: var(--binance-red) !important;
@@ -249,6 +264,7 @@ body {
border-color: var(--binance-yellow) !important; border-color: var(--binance-yellow) !important;
border-left: 3px solid 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-title,
.nofx-toast[data-type='warning'] .sonner-description, .nofx-toast[data-type='warning'] .sonner-description,
.nofx-toast[data-type='info'] .sonner-title, .nofx-toast[data-type='info'] .sonner-title,
@@ -259,6 +275,7 @@ body {
.nofx-toast .sonner-close-button { .nofx-toast .sonner-close-button {
color: var(--text-secondary) !important; color: var(--text-secondary) !important;
} }
.nofx-toast .sonner-close-button:hover { .nofx-toast .sonner-close-button:hover {
color: var(--text-primary) !important; color: var(--text-primary) !important;
} }
@@ -276,12 +293,24 @@ button {
} }
button:hover:not(:disabled) { button:hover:not(:disabled) {
filter: brightness(1.1);
filter: brightness(1.1); filter: brightness(1.1);
transform: translateY(-1px); transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
} }
button:active:not(:disabled) { button:active:not(:disabled) {
transform: scale(0.98) translateY(0); 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 { button:disabled {
@@ -289,13 +318,85 @@ button:disabled {
cursor: not-allowed; 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 { .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: 1px solid var(--panel-border);
border-radius: 4px; border-radius: 12px;
transition: all 0.2s ease; /* More modern radius */
box-shadow: var(--shadow-sm); 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 { .dev-toast-controller {
@@ -409,6 +510,7 @@ button:disabled {
border-color: var(--panel-border-hover); border-color: var(--panel-border-hover);
box-shadow: var(--shadow-md); box-shadow: var(--shadow-md);
transform: translateY(-2px); transform: translateY(-2px);
background: var(--panel-bg-hover);
} }
.binance-card-no-hover { .binance-card-no-hover {
@@ -420,19 +522,15 @@ button:disabled {
/* Binance gradient backgrounds */ /* Binance gradient backgrounds */
.binance-gradient { .binance-gradient {
background: linear-gradient( background: linear-gradient(135deg,
135deg, var(--binance-yellow) 0%,
var(--binance-yellow) 0%, var(--binance-yellow-light) 100%);
var(--binance-yellow-light) 100%
);
} }
.binance-gradient-subtle { .binance-gradient-subtle {
background: linear-gradient( background: linear-gradient(135deg,
135deg, rgba(240, 185, 11, 0.15) 0%,
rgba(240, 185, 11, 0.15) 0%, rgba(252, 213, 53, 0.05) 100%);
rgba(252, 213, 53, 0.05) 100%
);
border: 1px solid rgba(240, 185, 11, 0.2); border: 1px solid rgba(240, 185, 11, 0.2);
} }
@@ -466,12 +564,10 @@ button:disabled {
/* Skeleton loading */ /* Skeleton loading */
.skeleton { .skeleton {
background: var(--panel-bg); background: var(--panel-bg);
background-image: linear-gradient( background-image: linear-gradient(90deg,
90deg, transparent,
transparent, rgba(240, 185, 11, 0.05),
rgba(240, 185, 11, 0.05), transparent);
transparent
);
background-size: 200% 100%; background-size: 200% 100%;
animation: shimmer 1.5s infinite; animation: shimmer 1.5s infinite;
border-radius: 4px; border-radius: 4px;
@@ -644,12 +740,10 @@ tr:hover {
left: 0; left: 0;
right: 0; right: 0;
height: 2px; height: 2px;
background: linear-gradient( background: linear-gradient(90deg,
90deg, transparent,
transparent, var(--binance-yellow),
var(--binance-yellow), transparent);
transparent
);
opacity: 0; opacity: 0;
transition: opacity 0.2s ease; transition: opacity 0.2s ease;
} }