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 { 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)}

View File

@@ -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>
)

View File

@@ -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 */}

View File

@@ -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;
}
}
}