feat: add DeepVoidBackground and update UI theme across pages

- Add DeepVoidBackground component with animated gradient effects
- Apply nofx theme classes to StrategyStudioPage, AITradersPage, etc.
- Update styling for consistent dark theme with gold accents
- Add PageNotFound and TraderDashboardPage components
This commit is contained in:
tinkle-community
2026-01-04 17:49:59 +08:00
parent bdfd8dc0d0
commit 50923f6a2e
22 changed files with 3493 additions and 3960 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -145,43 +145,41 @@ export function ChartTabs({ traderId, selectedSymbol, updateKey, exchangeId }: C
console.log('[ChartTabs] rendering, activeTab:', activeTab)
return (
<div className="binance-card" style={{ background: '#0D1117', borderRadius: '8px', overflow: 'hidden' }}>
<div className="nofx-glass rounded-lg border border-white/5 relative z-10 w-full h-[600px] flex flex-col">
{/* Clean Professional Toolbar */}
<div
className="flex items-center justify-between px-3 py-1.5"
style={{ borderBottom: '1px solid rgba(43, 49, 57, 0.6)', background: '#161B22' }}
className="relative z-20 flex flex-wrap md:flex-nowrap items-center justify-between gap-y-2 px-3 py-2 shrink-0 backdrop-blur-md bg-[#0B0E11]/80 rounded-t-lg"
style={{ borderBottom: '1px solid rgba(255, 255, 255, 0.05)' }}
>
{/* Left: Tab Switcher */}
<div className="flex items-center gap-1">
<button
onClick={() => setActiveTab('equity')}
className={`flex items-center gap-1.5 px-2.5 py-1 rounded text-[11px] font-medium transition-all ${
activeTab === 'equity'
? 'bg-blue-500/15 text-blue-400'
: 'text-gray-500 hover:text-gray-300'
}`}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-md text-[11px] font-medium transition-all ${activeTab === 'equity'
? 'bg-nofx-gold/10 text-nofx-gold border border-nofx-gold/20 shadow-[0_0_10px_rgba(240,185,11,0.1)]'
: 'text-nofx-text-muted hover:text-nofx-text-main hover:bg-white/5'
}`}
>
<BarChart3 className="w-3 h-3" />
<BarChart3 className="w-3.5 h-3.5" />
<span>{t('accountEquityCurve', language)}</span>
</button>
<button
onClick={() => setActiveTab('kline')}
className={`flex items-center gap-1.5 px-2.5 py-1 rounded text-[11px] font-medium transition-all ${
activeTab === 'kline'
? 'bg-blue-500/15 text-blue-400'
: 'text-gray-500 hover:text-gray-300'
}`}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-md text-[11px] font-medium transition-all ${activeTab === 'kline'
? 'bg-nofx-gold/10 text-nofx-gold border border-nofx-gold/20 shadow-[0_0_10px_rgba(240,185,11,0.1)]'
: 'text-nofx-text-muted hover:text-nofx-text-main hover:bg-white/5'
}`}
>
<CandlestickChart className="w-3 h-3" />
<CandlestickChart className="w-3.5 h-3.5" />
<span>{t('marketChart', language)}</span>
</button>
{/* Market Type Pills - Only when kline active */}
{activeTab === 'kline' && (
<>
<div className="w-px h-3 bg-[#30363D] mx-1" />
<div className="flex items-center gap-0.5">
<div className="w-px h-4 bg-white/10 mx-2" />
<div className="flex items-center gap-1">
{(Object.keys(MARKET_CONFIG) as MarketType[]).map((type) => {
const config = MARKET_CONFIG[type]
const isActive = marketType === type
@@ -189,13 +187,13 @@ export function ChartTabs({ traderId, selectedSymbol, updateKey, exchangeId }: C
<button
key={type}
onClick={() => handleMarketTypeChange(type)}
className={`px-2 py-0.5 text-[10px] font-medium rounded transition-all ${
isActive
? 'bg-[#21262D] text-white'
: 'text-gray-500 hover:text-gray-400'
}`}
className={`px-2.5 py-1 text-[10px] font-medium rounded transition-all border ${isActive
? 'bg-white/10 text-white border-white/20'
: 'text-nofx-text-muted border-transparent hover:text-nofx-text-main hover:bg-white/5'
}`}
>
{config.icon} {language === 'zh' ? config.label.zh : config.label.en}
<span className="mr-1 opacity-70">{config.icon}</span>
{language === 'zh' ? config.label.zh : config.label.en}
</button>
)
})}
@@ -206,87 +204,89 @@ export function ChartTabs({ traderId, selectedSymbol, updateKey, exchangeId }: C
{/* Right: Symbol + Interval */}
{activeTab === 'kline' && (
<div className="flex items-center gap-2">
<div className="flex items-center gap-2 md:gap-3 w-full md:w-auto min-w-0">
{/* Symbol Dropdown */}
{marketConfig.hasDropdown ? (
<div className="relative" ref={dropdownRef}>
<button
onClick={() => setShowDropdown(!showDropdown)}
className="flex items-center gap-1 px-2 py-1 bg-[#21262D] rounded text-[11px] font-bold text-white hover:bg-[#30363D] transition-all"
>
<span>{chartSymbol}</span>
<ChevronDown className={`w-3 h-3 text-gray-400 transition-transform ${showDropdown ? 'rotate-180' : ''}`} />
</button>
{showDropdown && (
<div className="absolute top-full right-0 mt-1 w-56 bg-[#161B22] border border-[#30363D] rounded-lg shadow-2xl z-50 max-h-72 overflow-hidden">
<div className="p-2 border-b border-[#30363D]">
<div className="flex items-center gap-2 px-2 py-1 bg-[#0D1117] rounded border border-[#30363D]">
<Search className="w-3 h-3 text-gray-500" />
<input
type="text"
value={searchFilter}
onChange={(e) => setSearchFilter(e.target.value)}
placeholder="Search..."
className="flex-1 bg-transparent text-[11px] text-white placeholder-gray-600 focus:outline-none"
autoFocus
/>
<div className="shrink-0 relative" ref={dropdownRef}>
{marketConfig.hasDropdown ? (
<>
<button
onClick={() => setShowDropdown(!showDropdown)}
className="flex items-center gap-1.5 px-2.5 py-1 bg-black/40 border border-white/10 rounded text-[11px] font-bold text-nofx-text-main hover:border-nofx-gold/30 hover:text-nofx-gold transition-all"
>
<span>{chartSymbol}</span>
<ChevronDown className={`w-3 h-3 text-nofx-text-muted transition-transform ${showDropdown ? 'rotate-180' : ''}`} />
</button>
{showDropdown && (
<div className="absolute top-full right-0 mt-2 w-64 bg-[#0B0E11] border border-white/10 rounded-lg shadow-[0_10px_40px_-10px_rgba(0,0,0,0.5)] z-50 overflow-hidden nofx-glass ring-1 ring-white/5">
<div className="p-2 border-b border-white/5">
<div className="flex items-center gap-2 px-2 py-1.5 bg-black/40 rounded border border-white/10 focus-within:border-nofx-gold/50 transition-colors">
<Search className="w-3.5 h-3.5 text-nofx-text-muted" />
<input
type="text"
value={searchFilter}
onChange={(e) => setSearchFilter(e.target.value)}
placeholder="Search symbol..."
className="flex-1 bg-transparent text-[11px] text-white placeholder-gray-600 focus:outline-none font-mono"
autoFocus
/>
</div>
</div>
<div className="overflow-y-auto max-h-60 custom-scrollbar">
{['crypto', 'stock', 'forex', 'commodity', 'index'].map(category => {
const categorySymbols = filteredSymbols.filter(s => s.category === category)
if (categorySymbols.length === 0) return null
const labels: Record<string, string> = { crypto: 'Crypto', stock: 'Stocks', forex: 'Forex', commodity: 'Commodities', index: 'Index' }
return (
<div key={category}>
<div className="px-3 py-1.5 text-[9px] font-bold text-nofx-text-muted/60 bg-white/5 uppercase tracking-wider">{labels[category]}</div>
{categorySymbols.map(s => (
<button
key={s.symbol}
onClick={() => { setChartSymbol(s.symbol); setShowDropdown(false); setSearchFilter('') }}
className={`w-full px-3 py-2 text-left text-[11px] font-mono hover:bg-white/5 transition-all flex items-center justify-between ${chartSymbol === s.symbol ? 'bg-nofx-gold/10 text-nofx-gold' : 'text-nofx-text-muted'}`}
>
<span>{s.symbol}</span>
<span className="text-[9px] opacity-40">{s.name}</span>
</button>
))}
</div>
)
})}
</div>
</div>
<div className="overflow-y-auto max-h-52">
{['crypto', 'stock', 'forex', 'commodity', 'index'].map(category => {
const categorySymbols = filteredSymbols.filter(s => s.category === category)
if (categorySymbols.length === 0) return null
const labels: Record<string, string> = { crypto: 'Crypto', stock: 'Stocks', forex: 'Forex', commodity: 'Commodities', index: 'Index' }
return (
<div key={category}>
<div className="px-3 py-1 text-[9px] font-medium text-gray-500 bg-[#0D1117] uppercase tracking-wider">{labels[category]}</div>
{categorySymbols.map(s => (
<button
key={s.symbol}
onClick={() => { setChartSymbol(s.symbol); setShowDropdown(false); setSearchFilter('') }}
className={`w-full px-3 py-1.5 text-left text-[11px] hover:bg-[#21262D] transition-all ${chartSymbol === s.symbol ? 'bg-blue-500/20 text-blue-400' : 'text-gray-300'}`}
>
{s.symbol}
</button>
))}
</div>
)
})}
</div>
</div>
)}
</div>
) : (
<span className="px-2 py-1 bg-[#21262D] rounded text-[11px] font-bold text-white">{chartSymbol}</span>
)}
)}
</>
) : (
<span className="px-2.5 py-1 bg-black/40 border border-white/10 rounded text-[11px] font-bold text-nofx-text-main font-mono">{chartSymbol}</span>
)}
</div>
{/* Interval Selector */}
<div className="flex items-center bg-[#21262D] rounded overflow-hidden">
{/* Interval Selector - Allow scrolling if needed */}
<div className="flex items-center bg-black/40 rounded border border-white/10 overflow-x-auto no-scrollbar max-w-[200px] md:max-w-none">
{INTERVALS.map((int) => (
<button
key={int.value}
onClick={() => setInterval(int.value)}
className={`px-2 py-1 text-[10px] font-medium transition-all ${
interval === int.value
? 'bg-blue-500/30 text-blue-400'
: 'text-gray-500 hover:text-gray-300 hover:bg-[#30363D]'
}`}
className={`px-2 py-1 text-[10px] font-medium transition-all ${interval === int.value
? 'bg-nofx-gold/20 text-nofx-gold'
: 'text-nofx-text-muted hover:text-white hover:bg-white/5'
}`}
>
{int.label}
</button>
))}
</div>
{/* Quick Input */}
<form onSubmit={handleSymbolSubmit} className="flex items-center">
{/* Quick Input - Hidden on mobile, dropdown search is enough */}
<form onSubmit={handleSymbolSubmit} className="hidden md:flex items-center shrink-0">
<input
type="text"
value={symbolInput}
onChange={(e) => setSymbolInput(e.target.value)}
placeholder="Symbol..."
className="w-20 px-2 py-1 bg-[#0D1117] border border-[#30363D] rounded-l text-[10px] text-white placeholder-gray-600 focus:outline-none focus:border-blue-500/50"
placeholder="Sym"
className="w-16 px-2 py-1 bg-black/40 border border-white/10 rounded-l text-[10px] text-white placeholder-gray-600 focus:outline-none focus:border-nofx-gold/50 font-mono transition-colors"
/>
<button type="submit" className="px-2 py-1 bg-[#21262D] border border-[#30363D] border-l-0 rounded-r text-[10px] text-gray-400 hover:text-white hover:bg-[#30363D] transition-all">
<button type="submit" className="px-2 py-1 bg-white/5 border border-white/10 border-l-0 rounded-r text-[10px] text-nofx-text-muted hover:text-white hover:bg-white/10 transition-all">
Go
</button>
</form>
@@ -295,32 +295,33 @@ export function ChartTabs({ traderId, selectedSymbol, updateKey, exchangeId }: C
</div>
{/* Tab Content */}
<div className="relative overflow-hidden min-h-[400px]">
<div className="relative flex-1 bg-[#0B0E11]/50 rounded-b-lg overflow-hidden">
<AnimatePresence mode="wait">
{activeTab === 'equity' ? (
<motion.div
key="equity"
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 20 }}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
className="h-full"
className="h-full w-full absolute inset-0"
>
<EquityChart traderId={traderId} embedded />
</motion.div>
) : (
<motion.div
key={`kline-${chartSymbol}-${interval}-${currentExchange}`}
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
className="h-full"
className="h-full w-full absolute inset-0"
>
<AdvancedChart
symbol={chartSymbol}
interval={interval}
traderID={traderId}
// Dynamic height to fill container
height={550}
exchange={currentExchange}
onSymbolChange={setChartSymbol}

View File

@@ -9,6 +9,7 @@ import { getTraderColor } from '../utils/traderColors'
import { useLanguage } from '../contexts/LanguageContext'
import { t } from '../i18n/translations'
import { PunkAvatar, getTraderAvatar } from './PunkAvatar'
import { DeepVoidBackground } from './DeepVoidBackground'
export function CompetitionPage() {
const { language } = useLanguage()
@@ -44,83 +45,78 @@ export function CompetitionPage() {
if (!competition) {
return (
<div className="space-y-6">
<div className="binance-card p-8 animate-pulse">
<div className="flex items-center justify-between mb-6">
<div className="space-y-3 flex-1">
<div className="skeleton h-8 w-64"></div>
<div className="skeleton h-4 w-48"></div>
<DeepVoidBackground className="py-8" disableAnimation>
<div className="container mx-auto max-w-7xl px-4 md:px-8">
<div className="space-y-6">
<div className="animate-pulse bg-black/40 border border-white/10 rounded-xl p-8 backdrop-blur-md">
<div className="flex items-center justify-between mb-6">
<div className="space-y-3 flex-1">
<div className="h-8 w-64 bg-white/5 rounded"></div>
<div className="h-4 w-48 bg-white/5 rounded"></div>
</div>
<div className="h-12 w-32 bg-white/5 rounded"></div>
</div>
</div>
<div className="bg-black/40 border border-white/10 rounded-xl p-6 backdrop-blur-md">
<div className="h-6 w-40 mb-4 bg-white/5 rounded"></div>
<div className="space-y-3">
<div className="h-20 w-full bg-white/5 rounded"></div>
<div className="h-20 w-full bg-white/5 rounded"></div>
</div>
</div>
<div className="skeleton h-12 w-32"></div>
</div>
</div>
<div className="binance-card p-6">
<div className="skeleton h-6 w-40 mb-4"></div>
<div className="space-y-3">
<div className="skeleton h-20 w-full rounded"></div>
<div className="skeleton h-20 w-full rounded"></div>
</div>
</div>
</div>
</DeepVoidBackground>
)
}
// 如果有数据返回但没有交易员,显示空状态
if (!competition.traders || competition.traders.length === 0) {
return (
<div className="space-y-5 animate-fade-in">
{/* Competition Header - 精简版 */}
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-3 md:gap-0">
<div className="flex items-center gap-3 md:gap-4">
<div
className="w-10 h-10 md:w-12 md:h-12 rounded-xl flex items-center justify-center"
style={{
background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)',
boxShadow: '0 4px 14px rgba(240, 185, 11, 0.4)',
}}
>
<Trophy
className="w-6 h-6 md:w-7 md:h-7"
style={{ color: '#000' }}
/>
</div>
<div>
<h1
className="text-xl md:text-2xl font-bold flex items-center gap-2"
style={{ color: '#EAECEF' }}
<DeepVoidBackground className="py-8" disableAnimation>
<div className="container mx-auto max-w-7xl px-4 md:px-8 space-y-8 animate-fade-in">
{/* Competition Header - 精简版 */}
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-3 md:gap-0">
<div className="flex items-center gap-3 md:gap-4">
<div
className="w-10 h-10 md:w-12 md:h-12 rounded-xl flex items-center justify-center bg-black/60 border border-nofx-gold/30 shadow-[0_0_15px_rgba(240,185,11,0.2)]"
>
{t('aiCompetition', language)}
<span
className="text-xs font-normal px-2 py-1 rounded"
style={{
background: 'rgba(240, 185, 11, 0.15)',
color: '#F0B90B',
}}
<Trophy
className="w-6 h-6 md:w-7 md:h-7 text-nofx-gold"
/>
</div>
<div>
<h1
className="text-xl md:text-2xl font-bold flex items-center gap-2 text-white"
>
0 {t('traders', language)}
</span>
</h1>
<p className="text-xs" style={{ color: '#848E9C' }}>
{t('liveBattle', language)}
</p>
{t('aiCompetition', language)}
<span
className="text-xs font-normal px-2 py-1 rounded bg-nofx-gold/10 text-nofx-gold border border-nofx-gold/20"
>
0 {t('traders', language)}
</span>
</h1>
<p className="text-xs text-zinc-400">
{t('liveBattle', language)}
</p>
</div>
</div>
</div>
</div>
{/* Empty State */}
<div className="binance-card p-8 text-center">
<Trophy
className="w-16 h-16 mx-auto mb-4 opacity-40"
style={{ color: '#848E9C' }}
/>
<h3 className="text-lg font-bold mb-2" style={{ color: '#EAECEF' }}>
{t('noTraders', language)}
</h3>
<p className="text-sm" style={{ color: '#848E9C' }}>
{t('createFirstTrader', language)}
</p>
{/* Empty State */}
<div className="bg-black/40 border border-white/10 rounded-xl p-16 text-center backdrop-blur-md">
<Trophy
className="w-16 h-16 mx-auto mb-4 text-zinc-700"
/>
<h3 className="text-lg font-bold mb-2 text-white">
{t('noTraders', language)}
</h3>
<p className="text-sm text-zinc-400">
{t('createFirstTrader', language)}
</p>
</div>
</div>
</div>
</DeepVoidBackground>
)
}
@@ -133,375 +129,358 @@ export function CompetitionPage() {
const leader = sortedTraders[0]
return (
<div className="space-y-5 animate-fade-in">
{/* Competition Header - 精简版 */}
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-3 md:gap-0">
<div className="flex items-center gap-3 md:gap-4">
<div
className="w-10 h-10 md:w-12 md:h-12 rounded-xl flex items-center justify-center"
style={{
background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)',
boxShadow: '0 4px 14px rgba(240, 185, 11, 0.4)',
}}
>
<Trophy
className="w-6 h-6 md:w-7 md:h-7"
style={{ color: '#000' }}
/>
</div>
<div>
<h1
className="text-xl md:text-2xl font-bold flex items-center gap-2"
style={{ color: '#EAECEF' }}
<DeepVoidBackground className="py-8" disableAnimation>
<div className="w-full px-4 md:px-8 space-y-8 animate-fade-in">
{/* Competition Header - 精简版 */}
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-3 md:gap-0">
<div className="flex items-center gap-3 md:gap-4">
<div
className="w-10 h-10 md:w-12 md:h-12 rounded-xl flex items-center justify-center bg-black/60 border border-nofx-gold/30 shadow-[0_0_15px_rgba(240,185,11,0.2)]"
>
{t('aiCompetition', language)}
<span
className="text-xs font-normal px-2 py-1 rounded"
style={{
background: 'rgba(240, 185, 11, 0.15)',
color: '#F0B90B',
}}
<Trophy
className="w-6 h-6 md:w-7 md:h-7 text-nofx-gold"
/>
</div>
<div>
<h1
className="text-xl md:text-2xl font-bold flex items-center gap-2 text-white"
>
{competition.count} {t('traders', language)}
</span>
</h1>
<p className="text-xs" style={{ color: '#848E9C' }}>
{t('liveBattle', language)}
</p>
</div>
</div>
<div className="text-left md:text-right w-full md:w-auto">
<div className="text-xs mb-1" style={{ color: '#848E9C' }}>
{t('leader', language)}
</div>
<div
className="text-base md:text-lg font-bold"
style={{ color: '#F0B90B' }}
>
{leader?.trader_name}
</div>
<div
className="text-sm font-semibold"
style={{
color: (leader?.total_pnl ?? 0) >= 0 ? '#0ECB81' : '#F6465D',
}}
>
{(leader?.total_pnl ?? 0) >= 0 ? '+' : ''}
{leader?.total_pnl_pct?.toFixed(2) || '0.00'}%
</div>
</div>
</div>
{/* Left/Right Split: Performance Chart + Leaderboard */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-5">
{/* Left: Performance Comparison Chart */}
<div
className="binance-card p-5 animate-slide-in"
style={{ animationDelay: '0.1s' }}
>
<div className="flex items-center justify-between mb-4">
<h2
className="text-lg font-bold flex items-center gap-2"
style={{ color: '#EAECEF' }}
>
{t('performanceComparison', language)}
</h2>
<div className="text-xs" style={{ color: '#848E9C' }}>
{t('realTimePnL', language)}
{t('aiCompetition', language)}
<span
className="text-xs font-normal px-2 py-1 rounded bg-nofx-gold/10 text-nofx-gold border border-nofx-gold/20"
>
{competition.count} {t('traders', language)}
</span>
</h1>
<p className="text-xs text-zinc-400">
{t('liveBattle', language)}
</p>
</div>
</div>
<ComparisonChart traders={sortedTraders.slice(0, 10)} />
</div>
{/* Right: Leaderboard */}
<div
className="binance-card p-5 animate-slide-in"
style={{ animationDelay: '0.1s' }}
>
<div className="flex items-center justify-between mb-4">
<h2
className="text-lg font-bold flex items-center gap-2"
style={{ color: '#EAECEF' }}
>
{t('leaderboard', language)}
</h2>
<div className="text-left md:text-right w-full md:w-auto">
<div className="text-xs mb-1 text-zinc-400">
{t('leader', language)}
</div>
<div
className="text-xs px-2 py-1 rounded"
className="text-base md:text-lg font-bold text-nofx-gold"
>
{leader?.trader_name}
</div>
<div
className="text-sm font-semibold"
style={{
background: 'rgba(240, 185, 11, 0.1)',
color: '#F0B90B',
border: '1px solid rgba(240, 185, 11, 0.2)',
color: (leader?.total_pnl ?? 0) >= 0 ? '#0ECB81' : '#F6465D',
}}
>
{t('live', language)}
{(leader?.total_pnl ?? 0) >= 0 ? '+' : ''}
{leader?.total_pnl_pct?.toFixed(2) || '0.00'}%
</div>
</div>
<div className="space-y-2">
{sortedTraders.map((trader, index) => {
const isLeader = index === 0
const traderColor = getTraderColor(
sortedTraders,
trader.trader_id
)
</div>
return (
<div
key={trader.trader_id}
onClick={() => handleTraderClick(trader.trader_id)}
className="rounded p-3 transition-all duration-300 hover:translate-y-[-1px] cursor-pointer hover:shadow-lg"
style={{
background: isLeader
? 'linear-gradient(135deg, rgba(240, 185, 11, 0.08) 0%, #0B0E11 100%)'
: '#0B0E11',
border: `1px solid ${isLeader ? 'rgba(240, 185, 11, 0.4)' : '#2B3139'}`,
boxShadow: isLeader
? '0 3px 15px rgba(240, 185, 11, 0.12), 0 0 0 1px rgba(240, 185, 11, 0.15)'
: '0 1px 4px rgba(0, 0, 0, 0.3)',
}}
>
<div className="flex items-center justify-between">
{/* Rank & Avatar & Name */}
<div className="flex items-center gap-3">
{/* Rank Badge */}
<div
className="w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold"
style={{
background: index === 0
? 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)'
: index === 1
? 'linear-gradient(135deg, #C0C0C0 0%, #E8E8E8 100%)'
: index === 2
? 'linear-gradient(135deg, #CD7F32 0%, #E8A64C 100%)'
: '#2B3139',
color: index < 3 ? '#000' : '#848E9C',
}}
>
{index + 1}
</div>
{/* Punk Avatar */}
<PunkAvatar
seed={getTraderAvatar(trader.trader_id, trader.trader_name)}
size={36}
className="rounded-lg"
/>
<div>
<div
className="font-bold text-sm"
style={{ color: '#EAECEF' }}
>
{trader.trader_name}
</div>
<div
className="text-xs mono font-semibold"
style={{ color: traderColor }}
>
{trader.ai_model.toUpperCase()} +{' '}
{trader.exchange.toUpperCase()}
</div>
</div>
</div>
{/* Left/Right Split: Performance Chart + Leaderboard */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Left: Performance Comparison Chart */}
<div
className="bg-black/40 border border-white/10 rounded-xl p-6 backdrop-blur-md animate-slide-in hover:border-white/20 transition-colors"
style={{ animationDelay: '0.1s' }}
>
<div className="flex items-center justify-between mb-6">
<h2
className="text-lg font-bold flex items-center gap-2 text-white"
>
{t('performanceComparison', language)}
</h2>
<div className="text-xs text-zinc-400">
{t('realTimePnL', language)}
</div>
</div>
<ComparisonChart traders={sortedTraders.slice(0, 10)} />
</div>
{/* Stats */}
<div className="flex items-center gap-2 md:gap-3 flex-wrap md:flex-nowrap">
{/* Total Equity */}
<div className="text-right">
<div className="text-xs" style={{ color: '#848E9C' }}>
{t('equity', language)}
</div>
<div
className="text-xs md:text-sm font-bold mono"
style={{ color: '#EAECEF' }}
>
{trader.total_equity?.toFixed(2) || '0.00'}
</div>
</div>
{/* Right: Leaderboard */}
<div
className="bg-black/40 border border-white/10 rounded-xl p-6 backdrop-blur-md animate-slide-in hover:border-white/20 transition-colors"
style={{ animationDelay: '0.1s' }}
>
<div className="flex items-center justify-between mb-6">
<h2
className="text-lg font-bold flex items-center gap-2 text-white"
>
{t('leaderboard', language)}
</h2>
<div
className="text-xs px-2 py-1 rounded bg-nofx-gold/10 text-nofx-gold border border-nofx-gold/20 shadow-[0_0_8px_rgba(240,185,11,0.1)]"
>
{t('live', language)}
</div>
</div>
<div className="space-y-2">
{sortedTraders.map((trader, index) => {
const isLeader = index === 0
const traderColor = getTraderColor(
sortedTraders,
trader.trader_id
)
{/* P&L */}
<div className="text-right min-w-[70px] md:min-w-[90px]">
<div className="text-xs" style={{ color: '#848E9C' }}>
{t('pnl', language)}
</div>
return (
<div
key={trader.trader_id}
onClick={() => handleTraderClick(trader.trader_id)}
className="rounded p-3 transition-all duration-300 hover:translate-y-[-1px] cursor-pointer hover:shadow-lg"
style={{
background: isLeader
? 'linear-gradient(135deg, rgba(240, 185, 11, 0.08) 0%, #0B0E11 100%)'
: '#0B0E11',
border: `1px solid ${isLeader ? 'rgba(240, 185, 11, 0.4)' : '#2B3139'}`,
boxShadow: isLeader
? '0 3px 15px rgba(240, 185, 11, 0.12), 0 0 0 1px rgba(240, 185, 11, 0.15)'
: '0 1px 4px rgba(0, 0, 0, 0.3)',
}}
>
<div className="flex items-center justify-between">
{/* Rank & Avatar & Name */}
<div className="flex items-center gap-3">
{/* Rank Badge */}
<div
className="text-base md:text-lg font-bold mono"
className="w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold"
style={{
color:
(trader.total_pnl ?? 0) >= 0
? '#0ECB81'
: '#F6465D',
background: index === 0
? 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)'
: index === 1
? 'linear-gradient(135deg, #C0C0C0 0%, #E8E8E8 100%)'
: index === 2
? 'linear-gradient(135deg, #CD7F32 0%, #E8A64C 100%)'
: '#2B3139',
color: index < 3 ? '#000' : '#848E9C',
}}
>
{(trader.total_pnl ?? 0) >= 0 ? '+' : ''}
{trader.total_pnl_pct?.toFixed(2) || '0.00'}%
{index + 1}
</div>
<div
className="text-xs mono"
style={{ color: '#848E9C' }}
>
{(trader.total_pnl ?? 0) >= 0 ? '+' : ''}
{trader.total_pnl?.toFixed(2) || '0.00'}
{/* Punk Avatar */}
<PunkAvatar
seed={getTraderAvatar(trader.trader_id, trader.trader_name)}
size={36}
className="rounded-lg"
/>
<div>
<div
className="font-bold text-sm"
style={{ color: '#EAECEF' }}
>
{trader.trader_name}
</div>
<div
className="text-xs mono font-semibold"
style={{ color: traderColor }}
>
{trader.ai_model.toUpperCase()} +{' '}
{trader.exchange.toUpperCase()}
</div>
</div>
</div>
{/* Positions */}
<div className="text-right">
<div className="text-xs" style={{ color: '#848E9C' }}>
{t('pos', language)}
{/* Stats */}
<div className="flex items-center gap-2 md:gap-3 flex-wrap md:flex-nowrap">
{/* Total Equity */}
<div className="text-right">
<div className="text-xs" style={{ color: '#848E9C' }}>
{t('equity', language)}
</div>
<div
className="text-xs md:text-sm font-bold mono"
style={{ color: '#EAECEF' }}
>
{trader.total_equity?.toFixed(2) || '0.00'}
</div>
</div>
<div
className="text-xs md:text-sm font-bold mono"
style={{ color: '#EAECEF' }}
>
{trader.position_count}
</div>
<div className="text-xs" style={{ color: '#848E9C' }}>
{trader.margin_used_pct.toFixed(1)}%
</div>
</div>
{/* Status */}
<div>
<div
className="px-2 py-1 rounded text-xs font-bold"
style={
trader.is_running
? {
{/* P&L */}
<div className="text-right min-w-[70px] md:min-w-[90px]">
<div className="text-xs" style={{ color: '#848E9C' }}>
{t('pnl', language)}
</div>
<div
className="text-base md:text-lg font-bold mono"
style={{
color:
(trader.total_pnl ?? 0) >= 0
? '#0ECB81'
: '#F6465D',
}}
>
{(trader.total_pnl ?? 0) >= 0 ? '+' : ''}
{trader.total_pnl_pct?.toFixed(2) || '0.00'}%
</div>
<div
className="text-xs mono"
style={{ color: '#848E9C' }}
>
{(trader.total_pnl ?? 0) >= 0 ? '+' : ''}
{trader.total_pnl?.toFixed(2) || '0.00'}
</div>
</div>
{/* Positions */}
<div className="text-right">
<div className="text-xs" style={{ color: '#848E9C' }}>
{t('pos', language)}
</div>
<div
className="text-xs md:text-sm font-bold mono"
style={{ color: '#EAECEF' }}
>
{trader.position_count}
</div>
<div className="text-xs" style={{ color: '#848E9C' }}>
{trader.margin_used_pct.toFixed(1)}%
</div>
</div>
{/* Status */}
<div>
<div
className="px-2 py-1 rounded text-xs font-bold"
style={
trader.is_running
? {
background: 'rgba(14, 203, 129, 0.1)',
color: '#0ECB81',
}
: {
: {
background: 'rgba(246, 70, 93, 0.1)',
color: '#F6465D',
}
}
>
{trader.is_running ? '●' : '○'}
}
>
{trader.is_running ? '●' : '○'}
</div>
</div>
</div>
</div>
</div>
</div>
)
})}
)
})}
</div>
</div>
</div>
</div>
{/* Head-to-Head Stats */}
{competition.traders.length === 2 && (
<div
className="binance-card p-5 animate-slide-in"
style={{ animationDelay: '0.3s' }}
>
<h2
className="text-lg font-bold mb-4 flex items-center gap-2"
style={{ color: '#EAECEF' }}
{/* Head-to-Head Stats */}
{competition.traders.length === 2 && (
<div
className="bg-black/40 border border-white/10 rounded-xl p-6 backdrop-blur-md animate-slide-in"
style={{ animationDelay: '0.3s' }}
>
{t('headToHead', language)}
</h2>
<div className="grid grid-cols-2 gap-4">
{sortedTraders.map((trader, index) => {
const isWinning = index === 0
const opponent = sortedTraders[1 - index]
<h2
className="text-lg font-bold mb-6 flex items-center gap-2 text-white"
>
{t('headToHead', language)}
</h2>
<div className="grid grid-cols-2 gap-4">
{sortedTraders.map((trader, index) => {
const isWinning = index === 0
const opponent = sortedTraders[1 - index]
// Check if both values are valid numbers
const hasValidData =
trader.total_pnl_pct != null &&
opponent.total_pnl_pct != null &&
!isNaN(trader.total_pnl_pct) &&
!isNaN(opponent.total_pnl_pct)
// Check if both values are valid numbers
const hasValidData =
trader.total_pnl_pct != null &&
opponent.total_pnl_pct != null &&
!isNaN(trader.total_pnl_pct) &&
!isNaN(opponent.total_pnl_pct)
const gap = hasValidData
? trader.total_pnl_pct - opponent.total_pnl_pct
: NaN
const gap = hasValidData
? trader.total_pnl_pct - opponent.total_pnl_pct
: NaN
return (
<div
key={trader.trader_id}
className="p-4 rounded transition-all duration-300 hover:scale-[1.02]"
style={
isWinning
? {
return (
<div
key={trader.trader_id}
className="p-4 rounded transition-all duration-300 hover:scale-[1.02]"
style={
isWinning
? {
background:
'linear-gradient(135deg, rgba(14, 203, 129, 0.08) 0%, rgba(14, 203, 129, 0.02) 100%)',
border: '2px solid rgba(14, 203, 129, 0.3)',
boxShadow: '0 3px 15px rgba(14, 203, 129, 0.12)',
}
: {
: {
background: '#0B0E11',
border: '1px solid #2B3139',
boxShadow: '0 1px 4px rgba(0, 0, 0, 0.3)',
}
}
>
<div className="text-center">
{/* Avatar */}
<div className="flex justify-center mb-3">
<PunkAvatar
seed={getTraderAvatar(trader.trader_id, trader.trader_name)}
size={56}
className="rounded-xl"
/>
</div>
<div
className="text-sm md:text-base font-bold mb-2"
style={{
color: getTraderColor(sortedTraders, trader.trader_id),
}}
>
{trader.trader_name}
</div>
<div
className="text-lg md:text-2xl font-bold mono mb-1"
style={{
color:
(trader.total_pnl ?? 0) >= 0 ? '#0ECB81' : '#F6465D',
}}
>
{trader.total_pnl_pct != null &&
!isNaN(trader.total_pnl_pct)
? `${trader.total_pnl_pct >= 0 ? '+' : ''}${trader.total_pnl_pct.toFixed(2)}%`
: '—'}
</div>
{hasValidData && isWinning && gap > 0 && (
<div
className="text-xs font-semibold"
style={{ color: '#0ECB81' }}
>
{t('leadingBy', language, { gap: gap.toFixed(2) })}
}
>
<div className="text-center">
{/* Avatar */}
<div className="flex justify-center mb-3">
<PunkAvatar
seed={getTraderAvatar(trader.trader_id, trader.trader_name)}
size={56}
className="rounded-xl"
/>
</div>
)}
{hasValidData && !isWinning && gap < 0 && (
<div
className="text-xs font-semibold"
style={{ color: '#F6465D' }}
className="text-sm md:text-base font-bold mb-2"
style={{
color: getTraderColor(sortedTraders, trader.trader_id),
}}
>
{t('behindBy', language, {
gap: Math.abs(gap).toFixed(2),
})}
{trader.trader_name}
</div>
)}
{!hasValidData && (
<div
className="text-xs font-semibold"
style={{ color: '#848E9C' }}
className="text-lg md:text-2xl font-bold mono mb-1"
style={{
color:
(trader.total_pnl ?? 0) >= 0 ? '#0ECB81' : '#F6465D',
}}
>
{trader.total_pnl_pct != null &&
!isNaN(trader.total_pnl_pct)
? `${trader.total_pnl_pct >= 0 ? '+' : ''}${trader.total_pnl_pct.toFixed(2)}%`
: '—'}
</div>
)}
{hasValidData && isWinning && gap > 0 && (
<div
className="text-xs font-semibold"
style={{ color: '#0ECB81' }}
>
{t('leadingBy', language, { gap: gap.toFixed(2) })}
</div>
)}
{hasValidData && !isWinning && gap < 0 && (
<div
className="text-xs font-semibold"
style={{ color: '#F6465D' }}
>
{t('behindBy', language, {
gap: Math.abs(gap).toFixed(2),
})}
</div>
)}
{!hasValidData && (
<div
className="text-xs font-semibold"
style={{ color: '#848E9C' }}
>
</div>
)}
</div>
</div>
</div>
)
})}
)
})}
</div>
</div>
</div>
)}
)}
{/* Trader Config View Modal */}
<TraderConfigViewModal
isOpen={isModalOpen}
onClose={closeModal}
traderData={selectedTrader}
/>
</div>
{/* Trader Config View Modal */}
<TraderConfigViewModal
isOpen={isModalOpen}
onClose={closeModal}
traderData={selectedTrader}
/>
</div>
</DeepVoidBackground>
)
}

View File

@@ -0,0 +1,41 @@
import React from 'react'
import { motion } from 'framer-motion'
interface DeepVoidBackgroundProps extends React.HTMLAttributes<HTMLDivElement> {
children?: React.ReactNode
className?: string
disableAnimation?: boolean
}
export function DeepVoidBackground({ children, className = '', disableAnimation = false, ...props }: DeepVoidBackgroundProps) {
return (
<div className={`relative w-full min-h-screen bg-nofx-bg text-nofx-text overflow-hidden flex flex-col ${className}`} {...props}>
{/* BACKGROUND LAYERS */}
{/* 1. Grain/Noise Texture */}
<div className="absolute inset-0 bg-[url('https://grainy-gradients.vercel.app/noise.svg')] opacity-20 mix-blend-soft-light pointer-events-none fixed z-0"></div>
{/* 2. Grid System */}
<div className="absolute inset-0 pointer-events-none fixed z-0">
<div className="absolute inset-x-0 bottom-0 h-[50vh] bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:40px_40px] [mask-image:radial-gradient(ellipse_60%_50%_at_50%_0%,#000_70%,transparent_100%)] opacity-50" style={{ transform: 'perspective(500px) rotateX(60deg) translateY(100px) scale(2)' }}></div>
<div className="absolute inset-0 bg-grid-pattern opacity-[0.03]"></div>
</div>
{/* 3. Ambient Glow Spots */}
<div className="absolute inset-0 overflow-hidden pointer-events-none fixed z-0">
<div className="absolute top-[-10%] left-[-10%] w-[40vw] h-[40vw] bg-nofx-gold/10 rounded-full blur-[120px] mix-blend-screen animate-pulse-slow"></div>
<div className="absolute bottom-[-10%] right-[-10%] w-[40vw] h-[40vw] bg-nofx-accent/5 rounded-full blur-[120px] mix-blend-screen animate-pulse-slow" style={{ animationDelay: '2s' }}></div>
</div>
{/* 4. CRT/Scanline Overlay */}
<div className="absolute inset-0 pointer-events-none fixed z-[9999] opacity-40">
<div className="absolute inset-0 bg-[linear-gradient(rgba(18,16,16,0)_50%,rgba(0,0,0,0.25)_50%),linear-gradient(90deg,rgba(255,0,0,0.06),rgba(0,255,0,0.02),rgba(0,0,255,0.06))] bg-[length:100%_4px,3px_100%] pointer-events-none"></div>
</div>
{/* Content Layer */}
<div className="relative z-10 flex-1 flex flex-col h-full w-full">
{children}
</div>
</div>
)
}

View File

@@ -85,10 +85,7 @@ export default function HeaderBar({
className="flex items-center gap-2 hover:opacity-80 transition-opacity cursor-pointer"
>
<img src="/icons/nofx.svg" alt="NOFX Logo" className="w-7 h-7" />
<span
className="text-lg font-bold"
style={{ color: 'var(--brand-yellow)' }}
>
<span className="text-lg font-bold text-nofx-gold">
NOFX
</span>
</div>
@@ -128,28 +125,12 @@ export default function HeaderBar({
<button
key={tab.page}
onClick={() => handleNavClick(tab)}
className="text-sm font-bold transition-all duration-300 relative focus:outline-2 focus:outline-yellow-500"
style={{
color: currentPage === tab.page ? 'var(--brand-yellow)' : 'var(--brand-light-gray)',
padding: '8px 12px',
borderRadius: '8px',
position: 'relative',
}}
onMouseEnter={(e) => {
if (currentPage !== tab.page) {
e.currentTarget.style.color = 'var(--brand-yellow)'
}
}}
onMouseLeave={(e) => {
if (currentPage !== tab.page) {
e.currentTarget.style.color = 'var(--brand-light-gray)'
}
}}
className={`text-sm font-bold transition-all duration-300 relative focus:outline-2 focus:outline-yellow-500 px-3 py-2 rounded-lg
${currentPage === tab.page ? 'text-nofx-gold' : 'text-nofx-text-muted hover:text-nofx-gold'}`}
>
{currentPage === tab.page && (
<span
className="absolute inset-0 rounded-lg"
style={{ background: 'rgba(240, 185, 11, 0.15)', zIndex: -1 }}
className="absolute inset-0 rounded-lg bg-nofx-gold/15 -z-10"
/>
)}
{tab.label}
@@ -167,16 +148,7 @@ export default function HeaderBar({
href={OFFICIAL_LINKS.github}
target="_blank"
rel="noopener noreferrer"
className="p-2 rounded-lg transition-all hover:scale-110"
style={{ color: '#848E9C' }}
onMouseEnter={(e) => {
e.currentTarget.style.color = '#EAECEF'
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.05)'
}}
onMouseLeave={(e) => {
e.currentTarget.style.color = '#848E9C'
e.currentTarget.style.background = 'transparent'
}}
className="p-2 rounded-lg transition-all hover:scale-110 text-nofx-text-muted hover:text-white hover:bg-white/5"
title="GitHub"
>
<svg width="18" height="18" viewBox="0 0 16 16" fill="currentColor">
@@ -188,16 +160,7 @@ export default function HeaderBar({
href={OFFICIAL_LINKS.twitter}
target="_blank"
rel="noopener noreferrer"
className="p-2 rounded-lg transition-all hover:scale-110"
style={{ color: '#848E9C' }}
onMouseEnter={(e) => {
e.currentTarget.style.color = '#1DA1F2'
e.currentTarget.style.background = 'rgba(29, 161, 242, 0.1)'
}}
onMouseLeave={(e) => {
e.currentTarget.style.color = '#848E9C'
e.currentTarget.style.background = 'transparent'
}}
className="p-2 rounded-lg transition-all hover:scale-110 text-nofx-text-muted hover:text-[#1DA1F2] hover:bg-[#1DA1F2]/10"
title="Twitter"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
@@ -209,16 +172,7 @@ export default function HeaderBar({
href={OFFICIAL_LINKS.telegram}
target="_blank"
rel="noopener noreferrer"
className="p-2 rounded-lg transition-all hover:scale-110"
style={{ color: '#848E9C' }}
onMouseEnter={(e) => {
e.currentTarget.style.color = '#0088cc'
e.currentTarget.style.background = 'rgba(0, 136, 204, 0.1)'
}}
onMouseLeave={(e) => {
e.currentTarget.style.color = '#848E9C'
e.currentTarget.style.background = 'transparent'
}}
className="p-2 rounded-lg transition-all hover:scale-110 text-nofx-text-muted hover:text-[#0088cc] hover:bg-[#0088cc]/10"
title="Telegram"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
@@ -237,62 +191,24 @@ export default function HeaderBar({
<div className="relative" ref={userDropdownRef}>
<button
onClick={() => setUserDropdownOpen(!userDropdownOpen)}
className="flex items-center gap-2 px-3 py-2 rounded transition-colors"
style={{
background: 'var(--panel-bg)',
border: '1px solid var(--panel-border)',
}}
onMouseEnter={(e) =>
(e.currentTarget.style.background =
'rgba(255, 255, 255, 0.05)')
}
onMouseLeave={(e) =>
(e.currentTarget.style.background = 'var(--panel-bg)')
}
className="flex items-center gap-2 px-3 py-2 rounded transition-colors bg-nofx-bg-lighter border border-nofx-gold/20 hover:bg-white/5"
>
<div
className="w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold"
style={{
background: 'var(--brand-yellow)',
color: 'var(--brand-black)',
}}
>
<div className="w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold bg-nofx-gold text-black">
{user.email[0].toUpperCase()}
</div>
<span
className="text-sm"
style={{ color: 'var(--brand-light-gray)' }}
>
<span className="text-sm text-nofx-text-muted">
{user.email}
</span>
<ChevronDown
className="w-4 h-4"
style={{ color: 'var(--brand-light-gray)' }}
/>
<ChevronDown className="w-4 h-4 text-nofx-text-muted" />
</button>
{userDropdownOpen && (
<div
className="absolute right-0 top-full mt-2 w-48 rounded-lg shadow-lg overflow-hidden z-50"
style={{
background: 'var(--brand-dark-gray)',
border: '1px solid var(--panel-border)',
}}
>
<div
className="px-3 py-2 border-b"
style={{ borderColor: 'var(--panel-border)' }}
>
<div
className="text-xs"
style={{ color: 'var(--text-secondary)' }}
>
<div className="absolute right-0 top-full mt-2 w-48 rounded-lg shadow-lg overflow-hidden z-50 bg-nofx-bg-lighter border border-nofx-gold/20">
<div className="px-3 py-2 border-b border-nofx-gold/20">
<div className="text-xs text-nofx-text-muted">
{t('loggedInAs', language)}
</div>
<div
className="text-sm font-medium"
style={{ color: 'var(--brand-light-gray)' }}
>
<div className="text-sm font-medium text-nofx-text-muted">
{user.email}
</div>
</div>
@@ -302,11 +218,7 @@ export default function HeaderBar({
onLogout()
setUserDropdownOpen(false)
}}
className="w-full px-3 py-2 text-sm font-semibold transition-colors hover:opacity-80 text-center"
style={{
background: 'var(--binance-red-bg)',
color: 'var(--binance-red)',
}}
className="w-full px-3 py-2 text-sm font-semibold transition-colors hover:opacity-80 text-center bg-nofx-danger/20 text-nofx-danger"
>
{t('exitLogin', language)}
</button>
@@ -322,19 +234,14 @@ export default function HeaderBar({
<div className="flex items-center gap-3">
<a
href="/login"
className="px-3 py-2 text-sm font-medium transition-colors rounded"
style={{ color: 'var(--brand-light-gray)' }}
className="px-3 py-2 text-sm font-medium transition-colors rounded text-nofx-text-muted hover:text-white"
>
{t('signIn', language)}
</a>
{registrationEnabled && (
<a
href="/register"
className="px-4 py-2 rounded font-semibold text-sm transition-colors hover:opacity-90"
style={{
background: 'var(--brand-yellow)',
color: 'var(--brand-black)',
}}
className="px-4 py-2 rounded font-semibold text-sm transition-colors hover:opacity-90 bg-nofx-gold text-black"
>
{t('signUp', language)}
</a>
@@ -347,15 +254,7 @@ export default function HeaderBar({
<div className="relative" ref={dropdownRef}>
<button
onClick={() => setLanguageDropdownOpen(!languageDropdownOpen)}
className="flex items-center gap-2 px-3 py-2 rounded transition-colors"
style={{ color: 'var(--brand-light-gray)' }}
onMouseEnter={(e) =>
(e.currentTarget.style.background =
'rgba(255, 255, 255, 0.05)')
}
onMouseLeave={(e) =>
(e.currentTarget.style.background = 'transparent')
}
className="flex items-center gap-2 px-3 py-2 rounded transition-colors text-nofx-text-muted hover:bg-white/5"
>
<span className="text-lg">
{language === 'zh' ? '🇨🇳' : '🇺🇸'}
@@ -364,28 +263,14 @@ export default function HeaderBar({
</button>
{languageDropdownOpen && (
<div
className="absolute right-0 top-full mt-2 w-32 rounded-lg shadow-lg overflow-hidden z-50"
style={{
background: 'var(--brand-dark-gray)',
border: '1px solid var(--panel-border)',
}}
>
<div className="absolute right-0 top-full mt-2 w-32 rounded-lg shadow-lg overflow-hidden z-50 bg-nofx-bg-lighter border border-nofx-gold/20">
<button
onClick={() => {
onLanguageChange?.('zh')
setLanguageDropdownOpen(false)
}}
className={`w-full flex items-center gap-2 px-3 py-2 transition-colors ${
language === 'zh' ? '' : 'hover:opacity-80'
}`}
style={{
color: 'var(--brand-light-gray)',
background:
language === 'zh'
? 'rgba(240, 185, 11, 0.1)'
: 'transparent',
}}
className={`w-full flex items-center gap-2 px-3 py-2 transition-colors text-nofx-text-muted hover:text-white
${language === 'zh' ? 'bg-nofx-gold/10' : 'hover:bg-white/5'}`}
>
<span className="text-base">🇨🇳</span>
<span className="text-sm"></span>
@@ -395,16 +280,8 @@ export default function HeaderBar({
onLanguageChange?.('en')
setLanguageDropdownOpen(false)
}}
className={`w-full flex items-center gap-2 px-3 py-2 transition-colors ${
language === 'en' ? '' : 'hover:opacity-80'
}`}
style={{
color: 'var(--brand-light-gray)',
background:
language === 'en'
? 'rgba(240, 185, 11, 0.1)'
: 'transparent',
}}
className={`w-full flex items-center gap-2 px-3 py-2 transition-colors text-nofx-text-muted hover:text-white
${language === 'en' ? 'bg-nofx-gold/10' : 'hover:bg-white/5'}`}
>
<span className="text-base">🇺🇸</span>
<span className="text-sm">English</span>
@@ -418,8 +295,7 @@ export default function HeaderBar({
{/* Mobile Menu Button */}
<motion.button
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
className="md:hidden"
style={{ color: 'var(--brand-light-gray)' }}
className="md:hidden text-nofx-text-muted hover:text-white"
whileTap={{ scale: 0.9 }}
>
{mobileMenuOpen ? (
@@ -439,11 +315,7 @@ export default function HeaderBar({
: { height: 0, opacity: 0 }
}
transition={{ duration: 0.3 }}
className="md:hidden overflow-hidden"
style={{
background: 'var(--brand-dark-gray)',
borderTop: '1px solid rgba(240, 185, 11, 0.1)',
}}
className="md:hidden overflow-hidden bg-nofx-bg-lighter border-t border-nofx-gold/10"
>
<div className="px-4 py-4 space-y-2">
{/* Mobile Navigation Tabs - Show all tabs */}
@@ -476,25 +348,17 @@ export default function HeaderBar({
<button
key={tab.page}
onClick={() => handleMobileNavClick(tab)}
className="block text-sm font-bold transition-all duration-300 relative focus:outline-2 focus:outline-yellow-500"
style={{
color: currentPage === tab.page ? 'var(--brand-yellow)' : 'var(--brand-light-gray)',
padding: '12px 16px',
borderRadius: '8px',
position: 'relative',
width: '100%',
textAlign: 'left',
}}
className={`block text-sm font-bold transition-all duration-300 relative focus:outline-2 focus:outline-yellow-500 w-full text-left px-4 py-3 rounded-lg
${currentPage === tab.page ? 'text-nofx-gold' : 'text-nofx-text-muted hover:text-white hover:bg-white/5'}`}
>
{currentPage === tab.page && (
<span
className="absolute inset-0 rounded-lg"
style={{ background: 'rgba(240, 185, 11, 0.15)', zIndex: -1 }}
className="absolute inset-0 rounded-lg bg-nofx-gold/15 -z-10"
/>
)}
{tab.label}
{tab.requiresAuth && !isLoggedIn && (
<span className="ml-2 text-[10px] px-1.5 py-0.5 rounded" style={{ background: 'rgba(240, 185, 11, 0.2)', color: '#F0B90B' }}>
<span className="ml-2 text-[10px] px-1.5 py-0.5 rounded bg-nofx-gold/20 text-nofx-gold">
{language === 'zh' ? '需登录' : 'Login'}
</span>
)}
@@ -511,21 +375,19 @@ export default function HeaderBar({
<a
key={item.key}
href={`#${item.key === 'features' ? 'features' : 'how-it-works'}`}
className="block text-sm py-2"
style={{ color: 'var(--brand-light-gray)' }}
className="block text-sm py-2 text-nofx-text-muted hover:text-white"
>
{item.label}
</a>
))}
{/* Social Links - Mobile */}
<div className="py-3 flex items-center gap-3" style={{ borderTop: '1px solid #2B3139' }}>
<div className="py-3 flex items-center gap-3 border-t border-nofx-gold/20">
<a
href={OFFICIAL_LINKS.github}
target="_blank"
rel="noopener noreferrer"
className="p-2 rounded-lg"
style={{ color: '#848E9C', background: 'rgba(255, 255, 255, 0.05)' }}
className="p-2 rounded-lg text-nofx-text-muted bg-white/5 hover:text-white"
>
<svg width="20" height="20" viewBox="0 0 16 16" fill="currentColor">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z" />
@@ -535,8 +397,7 @@ export default function HeaderBar({
href={OFFICIAL_LINKS.twitter}
target="_blank"
rel="noopener noreferrer"
className="p-2 rounded-lg"
style={{ color: '#848E9C', background: 'rgba(255, 255, 255, 0.05)' }}
className="p-2 rounded-lg text-nofx-text-muted bg-white/5 hover:text-[#1DA1F2]"
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
@@ -546,8 +407,7 @@ export default function HeaderBar({
href={OFFICIAL_LINKS.telegram}
target="_blank"
rel="noopener noreferrer"
className="p-2 rounded-lg"
style={{ color: '#848E9C', background: 'rgba(255, 255, 255, 0.05)' }}
className="p-2 rounded-lg text-nofx-text-muted bg-white/5 hover:text-[#0088cc]"
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
<path d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z" />
@@ -558,10 +418,7 @@ export default function HeaderBar({
{/* Language Toggle */}
<div className="py-2">
<div className="flex items-center gap-2 mb-2">
<span
className="text-xs"
style={{ color: 'var(--brand-light-gray)' }}
>
<span className="text-xs text-nofx-text-muted">
{t('language', language)}:
</span>
</div>
@@ -571,11 +428,10 @@ export default function HeaderBar({
onLanguageChange?.('zh')
setMobileMenuOpen(false)
}}
className={`w-full flex items-center gap-3 px-3 py-2 rounded transition-colors ${
language === 'zh'
? 'bg-yellow-500 text-black'
: 'text-gray-400 hover:text-white'
}`}
className={`w-full flex items-center gap-3 px-3 py-2 rounded transition-colors ${language === 'zh'
? 'bg-yellow-500 text-black'
: 'text-gray-400 hover:text-white'
}`}
>
<span className="text-lg">🇨🇳</span>
<span className="text-sm"></span>
@@ -585,11 +441,10 @@ export default function HeaderBar({
onLanguageChange?.('en')
setMobileMenuOpen(false)
}}
className={`w-full flex items-center gap-3 px-3 py-2 rounded transition-colors ${
language === 'en'
? 'bg-yellow-500 text-black'
: 'text-gray-400 hover:text-white'
}`}
className={`w-full flex items-center gap-3 px-3 py-2 rounded transition-colors ${language === 'en'
? 'bg-yellow-500 text-black'
: 'text-gray-400 hover:text-white'
}`}
>
<span className="text-lg">🇺🇸</span>
<span className="text-sm">English</span>
@@ -600,33 +455,17 @@ export default function HeaderBar({
{/* User info and logout for mobile when logged in */}
{isLoggedIn && user && (
<div
className="mt-4 pt-4"
style={{ borderTop: '1px solid var(--panel-border)' }}
className="mt-4 pt-4 border-t border-nofx-gold/20"
>
<div
className="flex items-center gap-2 px-3 py-2 mb-2 rounded"
style={{ background: 'var(--panel-bg)' }}
>
<div
className="w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold"
style={{
background: 'var(--brand-yellow)',
color: 'var(--brand-black)',
}}
>
<div className="flex items-center gap-2 px-3 py-2 mb-2 rounded bg-nofx-bg-lighter">
<div className="w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold bg-nofx-gold text-black">
{user.email[0].toUpperCase()}
</div>
<div>
<div
className="text-xs"
style={{ color: 'var(--text-secondary)' }}
>
<div className="text-xs text-nofx-text-muted">
{t('loggedInAs', language)}
</div>
<div
className="text-sm"
style={{ color: 'var(--brand-light-gray)' }}
>
<div className="text-sm text-nofx-text-muted">
{user.email}
</div>
</div>
@@ -637,11 +476,7 @@ export default function HeaderBar({
onLogout()
setMobileMenuOpen(false)
}}
className="w-full px-4 py-2 rounded text-sm font-semibold transition-colors text-center"
style={{
background: 'var(--binance-red-bg)',
color: 'var(--binance-red)',
}}
className="w-full px-4 py-2 rounded text-sm font-semibold transition-colors text-center bg-nofx-danger/20 text-nofx-danger"
>
{t('exitLogin', language)}
</button>
@@ -656,11 +491,7 @@ export default function HeaderBar({
<div className="space-y-2 mt-2">
<a
href="/login"
className="block w-full px-4 py-2 rounded text-sm font-medium text-center transition-colors"
style={{
color: 'var(--brand-light-gray)',
border: '1px solid var(--brand-light-gray)',
}}
className="block w-full px-4 py-2 rounded text-sm font-medium text-center transition-colors text-nofx-text-muted border border-nofx-text-muted hover:text-white hover:border-white"
onClick={() => setMobileMenuOpen(false)}
>
{t('signIn', language)}
@@ -668,11 +499,7 @@ export default function HeaderBar({
{registrationEnabled && (
<a
href="/register"
className="block w-full px-4 py-2 rounded font-semibold text-sm text-center transition-colors"
style={{
background: 'var(--brand-yellow)',
color: 'var(--brand-black)',
}}
className="block w-full px-4 py-2 rounded font-semibold text-sm text-center transition-colors bg-nofx-gold text-black hover:opacity-90"
onClick={() => setMobileMenuOpen(false)}
>
{t('signUp', language)}

View File

@@ -3,6 +3,7 @@ import { useAuth } from '../contexts/AuthContext'
import { useLanguage } from '../contexts/LanguageContext'
import { t } from '../i18n/translations'
import { Eye, EyeOff } from 'lucide-react'
import { DeepVoidBackground } from './DeepVoidBackground'
// import { Input } from './ui/input' // Removed unused import
import { toast } from 'sonner'
import { useSystemConfig } from '../hooks/useSystemConfig'
@@ -102,13 +103,7 @@ export function LoginPage() {
}
return (
<div className="min-h-screen bg-black text-zinc-300 font-mono relative overflow-hidden flex items-center justify-center py-12">
{/* Background Effects */}
<div className="absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px] pointer-events-none"></div>
<div className="absolute inset-0 bg-gradient-to-t from-black via-transparent to-transparent pointer-events-none"></div>
{/* Scanline Effect */}
<div className="absolute inset-0 pointer-events-none opacity-[0.03] bg-[linear-gradient(transparent_50%,rgba(0,0,0,0.5)_50%)] bg-[length:100%_4px]"></div>
<DeepVoidBackground className="min-h-screen flex items-center justify-center py-12 font-mono" disableAnimation>
<div className="w-full max-w-md relative z-10 px-6">
{/* Navigation - Top Bar (Mobile/Desktop Friendly) */}
@@ -361,6 +356,6 @@ export function LoginPage() {
</div>
)}
</div>
</div>
</DeepVoidBackground>
)
}

View File

@@ -1,5 +1,6 @@
import { motion, AnimatePresence } from 'framer-motion'
import { LogIn, UserPlus, X, AlertTriangle, Terminal } from 'lucide-react'
import { DeepVoidBackground } from './DeepVoidBackground'
import { useLanguage } from '../contexts/LanguageContext'
interface LoginRequiredOverlayProps {
@@ -51,111 +52,114 @@ export function LoginRequiredOverlay({ isOpen, onClose, featureName }: LoginRequ
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/90 backdrop-blur-sm"
onClick={onClose}
className="fixed inset-0 z-50"
>
{/* Scanline Effect */}
<div className="absolute inset-0 pointer-events-none opacity-[0.03] bg-[linear-gradient(transparent_50%,rgba(0,0,0,0.5)_50%)] bg-[length:100%_4px]"></div>
<motion.div
initial={{ opacity: 0, scale: 0.95, y: 10 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 10 }}
transition={{ type: 'spring', damping: 20, stiffness: 300 }}
className="relative max-w-md w-full overflow-hidden bg-black border border-nofx-gold/30 shadow-[0_0_50px_rgba(240,185,11,0.1)] rounded-sm group font-mono"
onClick={(e) => e.stopPropagation()}
<DeepVoidBackground
className="w-full h-full bg-nofx-bg/95 backdrop-blur-md flex items-center justify-center p-4 text-nofx-text"
disableAnimation
onClick={onClose}
>
{/* Terminal Window Header */}
<div className="flex items-center justify-between px-3 py-2 bg-zinc-900 border-b border-zinc-800">
<div className="flex items-center gap-2">
<Terminal size={12} className="text-nofx-gold" />
<span className="text-[10px] text-zinc-500 uppercase tracking-wider">auth_protocol.exe</span>
<motion.div
initial={{ opacity: 0, scale: 0.95, y: 10 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 10 }}
transition={{ type: 'spring', damping: 20, stiffness: 300 }}
className="relative max-w-md w-full overflow-hidden bg-nofx-bg border border-nofx-gold/30 shadow-neon rounded-sm group font-mono"
onClick={(e) => e.stopPropagation()}
>
{/* Terminal Window Header */}
<div className="flex items-center justify-between px-3 py-2 bg-nofx-bg-lighter border-b border-nofx-gold/20">
<div className="flex items-center gap-2">
<Terminal size={12} className="text-nofx-gold" />
<span className="text-[10px] text-nofx-text-muted uppercase tracking-wider">auth_protocol.exe</span>
</div>
<button
onClick={onClose}
className="text-nofx-text-muted hover:text-nofx-danger transition-colors"
>
<X size={14} />
</button>
</div>
<button
onClick={onClose}
className="text-zinc-600 hover:text-red-500 transition-colors"
>
<X size={14} />
</button>
</div>
{/* Main Content */}
<div className="p-8 relative">
{/* Background Grid */}
<div className="absolute inset-0 bg-[linear-gradient(to_right,#80808008_1px,transparent_1px),linear-gradient(to_bottom,#80808008_1px,transparent_1px)] bg-[size:14px_14px] pointer-events-none"></div>
{/* Main Content */}
<div className="p-8 relative">
{/* Background Grid */}
<div className="absolute inset-0 bg-[linear-gradient(to_right,#80808008_1px,transparent_1px),linear-gradient(to_bottom,#80808008_1px,transparent_1px)] bg-[size:14px_14px] pointer-events-none"></div>
<div className="relative z-10">
{/* Flashing Access Denied */}
<div className="flex justify-center mb-6">
<div className="relative">
<div className="absolute inset-0 bg-red-500/20 blur-xl animate-pulse"></div>
<div className="bg-black border border-red-500/50 text-red-500 px-4 py-2 flex items-center gap-3 shadow-[0_0_15px_rgba(239,68,68,0.2)]">
<AlertTriangle size={18} className="animate-pulse" />
<span className="font-bold tracking-widest text-sm uppercase">{language === 'zh' ? '访问被拒绝' : 'ACCESS DENIED'}</span>
<div className="relative z-10">
{/* Flashing Access Denied */}
<div className="flex justify-center mb-6">
<div className="relative">
<div className="absolute inset-0 bg-red-500/20 blur-xl animate-pulse"></div>
<div className="bg-nofx-bg border border-red-500/50 text-red-500 px-4 py-2 flex items-center gap-3 shadow-[0_0_15px_rgba(239,68,68,0.2)]">
<AlertTriangle size={18} className="animate-pulse" />
<span className="font-bold tracking-widest text-sm uppercase">{language === 'zh' ? '访问被拒绝' : 'ACCESS DENIED'}</span>
</div>
</div>
</div>
</div>
{/* Terminal Text */}
<div className="space-y-4 mb-8">
<div className="text-center">
<h2 className="text-xl font-bold text-white uppercase tracking-wider mb-2">{t.title}</h2>
<p className="text-nofx-gold text-xs uppercase tracking-widest border-b border-nofx-gold/20 pb-4 inline-block">{t.subtitle}</p>
{/* Terminal Text */}
<div className="space-y-4 mb-8">
<div className="text-center">
<h2 className="text-xl font-bold text-white uppercase tracking-wider mb-2">{t.title}</h2>
<p className="text-nofx-gold text-xs uppercase tracking-widest border-b border-nofx-gold/20 pb-4 inline-block">{t.subtitle}</p>
</div>
<div className="bg-nofx-bg-lighter border-l-2 border-nofx-gold/20 p-3 my-4">
<p className="text-xs text-nofx-text-muted leading-relaxed font-mono">
<span className="text-green-500 mr-2">$</span>
{t.description}
</p>
</div>
<div className="grid grid-cols-2 gap-2">
{t.benefits.map((benefit, i) => (
<div key={i} className="flex items-center gap-2 text-[10px] text-nofx-text-muted uppercase tracking-wide">
<span className="text-nofx-gold"></span> {benefit}
</div>
))}
</div>
</div>
<div className="bg-zinc-900/50 border-l-2 border-zinc-700 p-3 my-4">
<p className="text-xs text-zinc-400 leading-relaxed font-mono">
<span className="text-green-500 mr-2">$</span>
{t.description}
</p>
{/* Action Buttons */}
<div className="space-y-3">
<a
href="/login"
className="flex items-center justify-center gap-2 w-full py-3 bg-nofx-gold text-black font-bold text-xs uppercase tracking-widest hover:bg-yellow-400 transition-all shadow-neon hover:shadow-[0_0_25px_rgba(240,185,11,0.4)] group"
>
<LogIn size={14} />
<span>{t.login}</span>
<span className="opacity-0 group-hover:opacity-100 transition-opacity -ml-2 group-hover:ml-0">-&gt;</span>
</a>
<a
href="/register"
className="flex items-center justify-center gap-2 w-full py-3 bg-transparent border border-nofx-gold/20 text-nofx-text-muted hover:text-white hover:border-nofx-gold font-bold text-xs uppercase tracking-widest transition-all hover:bg-nofx-gold/10"
>
<UserPlus size={14} />
<span>{t.register}</span>
</a>
</div>
<div className="grid grid-cols-2 gap-2">
{t.benefits.map((benefit, i) => (
<div key={i} className="flex items-center gap-2 text-[10px] text-zinc-500 uppercase tracking-wide">
<span className="text-nofx-gold"></span> {benefit}
</div>
))}
<div className="mt-4 text-center">
<button
onClick={onClose}
className="text-[10px] text-nofx-text-muted hover:text-nofx-danger uppercase tracking-widest hover:underline decoration-red-500/30"
>
[ {t.later} ]
</button>
</div>
</div>
{/* Action Buttons */}
<div className="space-y-3">
<a
href="/login"
className="flex items-center justify-center gap-2 w-full py-3 bg-nofx-gold text-black font-bold text-xs uppercase tracking-widest hover:bg-yellow-400 transition-all shadow-[0_0_15px_rgba(240,185,11,0.2)] hover:shadow-[0_0_25px_rgba(240,185,11,0.4)] group"
>
<LogIn size={14} />
<span>{t.login}</span>
<span className="opacity-0 group-hover:opacity-100 transition-opacity -ml-2 group-hover:ml-0">-&gt;</span>
</a>
<a
href="/register"
className="flex items-center justify-center gap-2 w-full py-3 bg-transparent border border-zinc-700 text-zinc-400 hover:text-white hover:border-zinc-500 font-bold text-xs uppercase tracking-widest transition-all hover:bg-zinc-900"
>
<UserPlus size={14} />
<span>{t.register}</span>
</a>
</div>
<div className="mt-4 text-center">
<button
onClick={onClose}
className="text-[10px] text-zinc-600 hover:text-red-500 uppercase tracking-widest hover:underline decoration-red-500/30"
>
[ {t.later} ]
</button>
</div>
</div>
</div>
{/* Corner Accents */}
<div className="absolute top-0 right-0 w-2 h-2 border-t border-r border-nofx-gold"></div>
<div className="absolute bottom-0 left-0 w-2 h-2 border-b border-l border-nofx-gold"></div>
{/* Corner Accents */}
<div className="absolute top-0 right-0 w-2 h-2 border-t border-r border-nofx-gold"></div>
<div className="absolute bottom-0 left-0 w-2 h-2 border-b border-l border-nofx-gold"></div>
</motion.div>
</motion.div>
</DeepVoidBackground>
</motion.div>
)}
</AnimatePresence>

View File

@@ -6,6 +6,7 @@ import { getSystemConfig } from '../lib/config'
import { toast } from 'sonner'
import { copyWithToast } from '../lib/clipboard'
import { Eye, EyeOff } from 'lucide-react'
import { DeepVoidBackground } from './DeepVoidBackground'
// import { Input } from './ui/input' // Removed unused import
import PasswordChecklist from 'react-password-checklist'
import { RegistrationDisabled } from './RegistrationDisabled'
@@ -148,13 +149,7 @@ export function RegisterPage() {
}
return (
<div className="min-h-screen bg-black text-zinc-300 font-mono relative overflow-hidden flex items-center justify-center py-12">
{/* Background Effects */}
<div className="absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px] pointer-events-none"></div>
<div className="absolute inset-0 bg-gradient-to-t from-black via-transparent to-transparent pointer-events-none"></div>
{/* Scanline Effect */}
<div className="absolute inset-0 pointer-events-none opacity-[0.03] bg-[linear-gradient(transparent_50%,rgba(0,0,0,0.5)_50%)] bg-[length:100%_4px]"></div>
<DeepVoidBackground className="min-h-screen flex items-center justify-center py-12 font-mono" disableAnimation>
<div className="w-full max-w-lg relative z-10 px-6">
{/* Navigation - Top Bar (Mobile/Desktop Friendly) */}
@@ -469,6 +464,6 @@ export function RegisterPage() {
)}
</div>
</div>
</DeepVoidBackground>
)
}

View File

@@ -16,7 +16,7 @@ export function WhitelistFullPage({ onBack }: WhitelistFullPageProps) {
}
return (
<div className="min-h-screen bg-black text-white font-mono relative overflow-hidden flex items-center justify-center px-4">
<div className="min-h-screen bg-nofx-bg-deeper text-white font-mono relative overflow-hidden flex items-center justify-center px-4">
{/* Background Grid & Scanlines */}
<div className="fixed inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px] pointer-events-none"></div>
<div className="fixed inset-0 bg-gradient-to-t from-black via-transparent to-transparent pointer-events-none"></div>

View File

@@ -56,14 +56,11 @@ export function FAQContent({
return (
<div className="space-y-12">
{categories.map((category) => (
<div key={category.id}>
<div key={category.id} className="nofx-glass p-8 rounded-xl border border-white/5">
{/* Category Header */}
<div
className="flex items-center gap-3 mb-6 pb-3"
style={{ borderBottom: '2px solid #2B3139' }}
>
<category.icon className="w-7 h-7" style={{ color: '#F0B90B' }} />
<h2 className="text-2xl font-bold" style={{ color: '#EAECEF' }}>
<div className="flex items-center gap-3 mb-6 pb-3 border-b border-white/10">
<category.icon className="w-7 h-7 text-nofx-gold" />
<h2 className="text-2xl font-bold text-nofx-text-main">
{t(category.titleKey, language)}
</h2>
</div>
@@ -79,21 +76,12 @@ export function FAQContent({
className="scroll-mt-24"
>
{/* Question */}
<h3
className="text-xl font-semibold mb-3"
style={{ color: '#EAECEF' }}
>
<h3 className="text-xl font-semibold mb-3 text-nofx-text-main">
{t(item.questionKey, language)}
</h3>
{/* Answer */}
<div
className="prose prose-invert max-w-none"
style={{
color: '#B7BDC6',
lineHeight: '1.7',
}}
>
<div className="prose prose-invert max-w-none text-nofx-text-muted leading-relaxed">
{item.id === 'github-projects-tasks' ? (
<div className="space-y-3">
<div className="text-base">
@@ -295,7 +283,7 @@ export function FAQContent({
href="https://github.com/NoFxAiOS/nofx/blob/dev/CONTRIBUTING.md"
target="_blank"
rel="noreferrer"
style={{ color: '#F0B90B' }}
className="text-nofx-gold hover:underline"
>
CONTRIBUTING.md
</a>
@@ -304,7 +292,7 @@ export function FAQContent({
href="https://github.com/NoFxAiOS/nofx/blob/dev/.github/PR_TITLE_GUIDE.md"
target="_blank"
rel="noreferrer"
style={{ color: '#F0B90B' }}
className="text-nofx-gold hover:underline"
>
PR_TITLE_GUIDE.md
</a>
@@ -383,16 +371,10 @@ export function FAQContent({
)}
</ol>
<div
className="rounded p-3 mt-3"
style={{
background: 'rgba(240, 185, 11, 0.08)',
border: '1px solid rgba(240, 185, 11, 0.25)',
}}
>
<div className="rounded p-3 mt-3 bg-nofx-gold/10 border border-nofx-gold/25">
{language === 'zh' ? (
<div className="text-sm">
<strong style={{ color: '#F0B90B' }}>提示:</strong>{' '}
<strong className="text-nofx-gold">Note:</strong>{' '}
我们为高质量贡献提供激励Bounty/奖金、荣誉徽章与鸣谢、优先
Review/合并与内测资格 等)。 详情可关注带
<a
@@ -448,7 +430,7 @@ export function FAQContent({
</div>
{/* Divider */}
<div className="mt-6 h-px" style={{ background: '#2B3139' }} />
<div className="mt-6 h-px bg-white/5" />
</section>
))}
</div>

View File

@@ -1,6 +1,6 @@
import { useState, useMemo } from 'react'
import { HelpCircle } from 'lucide-react'
import { Container } from '../Container'
import { DeepVoidBackground } from '../DeepVoidBackground'
import { t, type Language } from '../../i18n/translations'
import { FAQSearchBar } from './FAQSearchBar'
import { FAQSidebar } from './FAQSidebar'
@@ -58,125 +58,121 @@ export function FAQLayout({ language }: FAQLayoutProps) {
}
return (
<Container className="py-6 pt-24">
{/* Page Header */}
<div className="text-center mb-12">
<div className="flex items-center justify-center gap-3 mb-4">
<div
className="w-16 h-16 rounded-full flex items-center justify-center"
style={{
background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)',
boxShadow: '0 8px 24px rgba(240, 185, 11, 0.4)',
}}
>
<HelpCircle className="w-8 h-8" style={{ color: '#0B0E11' }} />
<DeepVoidBackground className="py-6 pt-24" disableAnimation>
<div className="w-full px-4 md:px-8">
{/* Page Header */}
<div className="text-center mb-12">
<div className="flex items-center justify-center gap-3 mb-4">
<div className="w-16 h-16 rounded-full flex items-center justify-center bg-gradient-to-br from-nofx-gold to-[#FCD535] shadow-[0_8px_24px_rgba(240,185,11,0.4)]">
<HelpCircle className="w-8 h-8 text-[#0B0E11]" />
</div>
</div>
<h1 className="text-4xl font-bold mb-4 text-nofx-text-main">
{t('faqTitle', language)}
</h1>
<p className="text-lg mb-8 text-nofx-text-muted">
{t('faqSubtitle', language)}
</p>
{/* Search Bar */}
<div className="max-w-2xl mx-auto">
<FAQSearchBar
searchTerm={searchTerm}
onSearchChange={setSearchTerm}
placeholder={
language === 'zh' ? '搜索常见问题...' : 'Search FAQ...'
}
/>
</div>
</div>
<h1 className="text-4xl font-bold mb-4" style={{ color: '#EAECEF' }}>
{t('faqTitle', language)}
</h1>
<p className="text-lg mb-8" style={{ color: '#848E9C' }}>
{t('faqSubtitle', language)}
</p>
{/* Search Bar */}
<div className="max-w-2xl mx-auto">
<FAQSearchBar
searchTerm={searchTerm}
onSearchChange={setSearchTerm}
placeholder={
language === 'zh' ? '搜索常见问题...' : 'Search FAQ...'
}
/>
</div>
</div>
{/* Main Content */}
<div className="flex gap-8">
{/* Sidebar - Hidden on mobile, visible on desktop */}
<aside className="hidden lg:block w-64 flex-shrink-0">
<FAQSidebar
categories={filteredCategories}
activeItemId={activeItemId}
language={language}
onItemClick={handleItemClick}
/>
</aside>
{/* Content Area */}
<main className="flex-1 min-w-0">
{filteredCategories.length > 0 ? (
<FAQContent
{/* Main Content */}
<div className="flex gap-8">
{/* Sidebar - Hidden on mobile, visible on desktop */}
<aside className="hidden lg:block w-64 flex-shrink-0">
<FAQSidebar
categories={filteredCategories}
activeItemId={activeItemId}
language={language}
onActiveItemChange={setActiveItemId}
onItemClick={handleItemClick}
/>
) : (
<div className="text-center py-12">
<p className="text-lg" style={{ color: '#848E9C' }}>
{language === 'zh'
? '没有找到匹配的问题'
: 'No matching questions found'}
</p>
<button
onClick={() => setSearchTerm('')}
className="mt-4 px-6 py-2 rounded-lg font-semibold transition-all hover:opacity-90"
style={{
background:
'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)',
color: '#0B0E11',
}}
>
{language === 'zh' ? '清除搜索' : 'Clear Search'}
</button>
</div>
)}
</main>
</div>
</aside>
{/* Contact Section */}
<div
className="mt-16 p-8 rounded-lg text-center"
style={{
background:
'linear-gradient(135deg, rgba(240, 185, 11, 0.1) 0%, rgba(252, 213, 53, 0.05) 100%)',
border: '1px solid rgba(240, 185, 11, 0.2)',
}}
>
<h3 className="text-xl font-bold mb-3" style={{ color: '#EAECEF' }}>
{t('faqStillHaveQuestions', language)}
</h3>
<p className="mb-6" style={{ color: '#848E9C' }}>
{t('faqContactUs', language)}
</p>
<div className="flex items-center justify-center gap-4">
<a
href="https://github.com/NoFxAiOS/nofx"
target="_blank"
rel="noopener noreferrer"
className="px-6 py-3 rounded-lg font-semibold transition-all hover:scale-105"
style={{
background: '#1E2329',
color: '#EAECEF',
border: '1px solid #2B3139',
}}
>
GitHub
</a>
<a
href="https://t.me/nofx_dev_community"
target="_blank"
rel="noopener noreferrer"
className="px-6 py-3 rounded-lg font-semibold transition-all hover:scale-105"
style={{
background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)',
color: '#0B0E11',
}}
>
{t('community', language)}
</a>
{/* Content Area */}
<main className="flex-1 min-w-0">
{filteredCategories.length > 0 ? (
<FAQContent
categories={filteredCategories}
language={language}
onActiveItemChange={setActiveItemId}
/>
) : (
<div className="text-center py-12">
<p className="text-lg" style={{ color: '#848E9C' }}>
{language === 'zh'
? '没有找到匹配的问题'
: 'No matching questions found'}
</p>
<button
onClick={() => setSearchTerm('')}
className="mt-4 px-6 py-2 rounded-lg font-semibold transition-all hover:opacity-90"
style={{
background:
'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)',
color: '#0B0E11',
}}
>
{language === 'zh' ? '清除搜索' : 'Clear Search'}
</button>
</div>
)}
</main>
</div>
{/* Contact Section */}
<div
className="mt-16 p-8 rounded-lg text-center"
style={{
background:
'linear-gradient(135deg, rgba(240, 185, 11, 0.1) 0%, rgba(252, 213, 53, 0.05) 100%)',
border: '1px solid rgba(240, 185, 11, 0.2)',
}}
>
<h3 className="text-xl font-bold mb-3" style={{ color: '#EAECEF' }}>
{t('faqStillHaveQuestions', language)}
</h3>
<p className="mb-6" style={{ color: '#848E9C' }}>
{t('faqContactUs', language)}
</p>
<div className="flex items-center justify-center gap-4">
<a
href="https://github.com/NoFxAiOS/nofx"
target="_blank"
rel="noopener noreferrer"
className="px-6 py-3 rounded-lg font-semibold transition-all hover:scale-105"
style={{
background: '#1E2329',
color: '#EAECEF',
border: '1px solid #2B3139',
}}
>
GitHub
</a>
<a
href="https://t.me/nofx_dev_community"
target="_blank"
rel="noopener noreferrer"
className="px-6 py-3 rounded-lg font-semibold transition-all hover:scale-105"
style={{
background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)',
color: '#0B0E11',
}}
>
{t('community', language)}
</a>
</div>
</div>
</div>
</Container>
</DeepVoidBackground>
)
}

View File

@@ -12,36 +12,21 @@ export function FAQSearchBar({
placeholder = 'Search FAQ...',
}: FAQSearchBarProps) {
return (
<div className="relative">
<div className="relative group">
<Search
className="absolute left-4 top-1/2 transform -translate-y-1/2 w-5 h-5"
style={{ color: '#848E9C' }}
className="absolute left-4 top-1/2 transform -translate-y-1/2 w-5 h-5 text-nofx-text-muted group-focus-within:text-nofx-gold transition-colors"
/>
<input
type="text"
value={searchTerm}
onChange={(e) => onSearchChange(e.target.value)}
placeholder={placeholder}
className="w-full pl-12 pr-12 py-3 rounded-lg text-base transition-all focus:outline-none focus:ring-2"
style={{
background: '#1E2329',
border: '1px solid #2B3139',
color: '#EAECEF',
}}
onFocus={(e) => {
e.target.style.borderColor = '#F0B90B'
e.target.style.boxShadow = '0 0 0 3px rgba(240, 185, 11, 0.1)'
}}
onBlur={(e) => {
e.target.style.borderColor = '#2B3139'
e.target.style.boxShadow = 'none'
}}
className="w-full pl-12 pr-12 py-3 rounded-lg text-base transition-all focus:outline-none bg-black/40 border border-white/10 text-nofx-text-main placeholder-nofx-text-muted/50 focus:border-nofx-gold/50 focus:ring-1 focus:ring-nofx-gold/20 hover:border-nofx-gold/30 font-mono"
/>
{searchTerm && (
<button
onClick={() => onSearchChange('')}
className="absolute right-4 top-1/2 transform -translate-y-1/2 hover:opacity-70 transition-opacity"
style={{ color: '#848E9C' }}
className="absolute right-4 top-1/2 transform -translate-y-1/2 text-nofx-text-muted hover:text-white transition-colors"
>
<X className="w-5 h-5" />
</button>

View File

@@ -24,14 +24,11 @@ export function FAQSidebar({
>
<div className="space-y-6">
{categories.map((category) => (
<div key={category.id}>
<div key={category.id} className="nofx-glass p-4 rounded-xl border border-white/5">
{/* Category Title */}
<div className="flex items-center gap-2 mb-3 px-3">
<category.icon className="w-5 h-5" style={{ color: '#F0B90B' }} />
<h3
className="text-sm font-bold uppercase tracking-wide"
style={{ color: '#F0B90B' }}
>
<category.icon className="w-5 h-5 text-nofx-gold" />
<h3 className="text-sm font-bold uppercase tracking-wide text-nofx-gold">
{t(category.titleKey, language)}
</h3>
</div>
@@ -44,30 +41,10 @@ export function FAQSidebar({
<li key={item.id}>
<button
onClick={() => onItemClick(category.id, item.id)}
className="w-full text-left px-3 py-2 rounded-lg text-sm transition-all"
style={{
background: isActive
? 'rgba(240, 185, 11, 0.1)'
: 'transparent',
color: isActive ? '#F0B90B' : '#848E9C',
borderLeft: isActive
? '3px solid #F0B90B'
: '3px solid transparent',
paddingLeft: isActive ? '9px' : '12px',
}}
onMouseEnter={(e) => {
if (!isActive) {
e.currentTarget.style.background =
'rgba(240, 185, 11, 0.05)'
e.currentTarget.style.color = '#EAECEF'
}
}}
onMouseLeave={(e) => {
if (!isActive) {
e.currentTarget.style.background = 'transparent'
e.currentTarget.style.color = '#848E9C'
}
}}
className={`w-full text-left px-3 py-2 rounded-lg text-sm transition-all border-l-[3px] ${isActive
? 'bg-nofx-gold/10 text-nofx-gold border-nofx-gold pl-[9px]'
: 'bg-transparent text-nofx-text-muted border-transparent pl-3 hover:bg-nofx-gold/5 hover:text-nofx-text-main'
}`}
>
{t(item.questionKey, language)}
</button>

View File

@@ -143,12 +143,7 @@ export function CoinSourceEditor({
// NofxOS badge component
const NofxOSBadge = () => (
<span
className="text-[9px] px-1.5 py-0.5 rounded font-medium"
style={{
background: 'linear-gradient(135deg, rgba(99, 102, 241, 0.2), rgba(168, 85, 247, 0.2))',
color: '#a855f7',
border: '1px solid rgba(139, 92, 246, 0.3)'
}}
className="text-[9px] px-1.5 py-0.5 rounded font-medium bg-purple-500/20 text-purple-400 border border-purple-500/30"
>
NofxOS
</span>
@@ -158,7 +153,7 @@ export function CoinSourceEditor({
<div className="space-y-6">
{/* Source Type Selector */}
<div>
<label className="block text-sm font-medium mb-3" style={{ color: '#EAECEF' }}>
<label className="block text-sm font-medium mb-3 text-nofx-text">
{t('sourceType')}
</label>
<div className="grid grid-cols-4 gap-3">
@@ -170,24 +165,16 @@ export function CoinSourceEditor({
onChange({ ...config, source_type: value as CoinSourceConfig['source_type'] })
}
disabled={disabled}
className={`p-4 rounded-lg border transition-all ${
config.source_type === value
? 'ring-2 ring-yellow-500'
: 'hover:bg-white/5'
}`}
style={{
background:
config.source_type === value
? 'rgba(240, 185, 11, 0.1)'
: '#0B0E11',
borderColor: '#2B3139',
}}
className={`p-4 rounded-lg border transition-all ${config.source_type === value
? 'ring-2 ring-nofx-gold bg-nofx-gold/10'
: 'hover:bg-white/5 bg-nofx-bg'
} border-nofx-gold/20`}
>
<Icon className="w-6 h-6 mx-auto mb-2" style={{ color }} />
<div className="text-sm font-medium" style={{ color: '#EAECEF' }}>
<div className="text-sm font-medium text-nofx-text">
{t(value)}
</div>
<div className="text-xs mt-1" style={{ color: '#848E9C' }}>
<div className="text-xs mt-1 text-nofx-text-muted">
{t(`${value}Desc`)}
</div>
</button>
@@ -198,15 +185,14 @@ export function CoinSourceEditor({
{/* Static Coins */}
{(config.source_type === 'static' || config.source_type === 'mixed') && (
<div>
<label className="block text-sm font-medium mb-3" style={{ color: '#EAECEF' }}>
<label className="block text-sm font-medium mb-3 text-nofx-text">
{t('staticCoins')}
</label>
<div className="flex flex-wrap gap-2 mb-3">
{(config.static_coins || []).map((coin) => (
<span
key={coin}
className="flex items-center gap-1 px-3 py-1.5 rounded-full text-sm"
style={{ background: '#2B3139', color: '#EAECEF' }}
className="flex items-center gap-1 px-3 py-1.5 rounded-full text-sm bg-nofx-bg-lighter text-nofx-text"
>
{coin}
{!disabled && (
@@ -228,17 +214,11 @@ export function CoinSourceEditor({
onChange={(e) => setNewCoin(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleAddCoin()}
placeholder="BTC, ETH, SOL..."
className="flex-1 px-4 py-2 rounded-lg"
style={{
background: '#0B0E11',
border: '1px solid #2B3139',
color: '#EAECEF',
}}
className="flex-1 px-4 py-2 rounded-lg bg-nofx-bg border border-nofx-gold/20 text-nofx-text"
/>
<button
onClick={handleAddCoin}
className="px-4 py-2 rounded-lg flex items-center gap-2 transition-colors"
style={{ background: '#F0B90B', color: '#0B0E11' }}
className="px-4 py-2 rounded-lg flex items-center gap-2 transition-colors bg-nofx-gold text-black hover:bg-yellow-500"
>
<Plus className="w-4 h-4" />
{t('addCoin')}
@@ -251,20 +231,19 @@ export function CoinSourceEditor({
{/* Excluded Coins */}
<div>
<div className="flex items-center gap-2 mb-3">
<Ban className="w-4 h-4" style={{ color: '#F6465D' }} />
<label className="text-sm font-medium" style={{ color: '#EAECEF' }}>
<Ban className="w-4 h-4 text-nofx-danger" />
<label className="text-sm font-medium text-nofx-text">
{t('excludedCoins')}
</label>
</div>
<p className="text-xs mb-3" style={{ color: '#848E9C' }}>
<p className="text-xs mb-3 text-nofx-text-muted">
{t('excludedCoinsDesc')}
</p>
<div className="flex flex-wrap gap-2 mb-3">
{(config.excluded_coins || []).map((coin) => (
<span
key={coin}
className="flex items-center gap-1 px-3 py-1.5 rounded-full text-sm"
style={{ background: 'rgba(246, 70, 93, 0.15)', color: '#F6465D' }}
className="flex items-center gap-1 px-3 py-1.5 rounded-full text-sm bg-nofx-danger/15 text-nofx-danger"
>
{coin}
{!disabled && (
@@ -278,7 +257,7 @@ export function CoinSourceEditor({
</span>
))}
{(config.excluded_coins || []).length === 0 && (
<span className="text-xs italic" style={{ color: '#5E6673' }}>
<span className="text-xs italic text-nofx-text-muted">
{language === 'zh' ? '无' : 'None'}
</span>
)}
@@ -291,17 +270,11 @@ export function CoinSourceEditor({
onChange={(e) => setNewExcludedCoin(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleAddExcludedCoin()}
placeholder="BTC, ETH, DOGE..."
className="flex-1 px-4 py-2 rounded-lg text-sm"
style={{
background: '#0B0E11',
border: '1px solid #2B3139',
color: '#EAECEF',
}}
className="flex-1 px-4 py-2 rounded-lg text-sm bg-nofx-bg border border-nofx-gold/20 text-nofx-text"
/>
<button
onClick={handleAddExcludedCoin}
className="px-4 py-2 rounded-lg flex items-center gap-2 transition-colors text-sm"
style={{ background: '#F6465D', color: '#EAECEF' }}
className="px-4 py-2 rounded-lg flex items-center gap-2 transition-colors text-sm bg-nofx-danger text-white hover:bg-red-600"
>
<Ban className="w-4 h-4" />
{t('addExcludedCoin')}
@@ -313,16 +286,12 @@ export function CoinSourceEditor({
{/* AI500 Options */}
{(config.source_type === 'ai500' || config.source_type === 'mixed') && (
<div
className="p-4 rounded-lg"
style={{
background: 'rgba(240, 185, 11, 0.05)',
border: '1px solid rgba(240, 185, 11, 0.2)',
}}
className="p-4 rounded-lg bg-nofx-gold/5 border border-nofx-gold/20"
>
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<Zap className="w-4 h-4" style={{ color: '#F0B90B' }} />
<span className="text-sm font-medium" style={{ color: '#EAECEF' }}>
<Zap className="w-4 h-4 text-nofx-gold" />
<span className="text-sm font-medium text-nofx-text">
AI500 {t('dataSourceConfig')}
</span>
<NofxOSBadge />
@@ -338,14 +307,14 @@ export function CoinSourceEditor({
!disabled && onChange({ ...config, use_ai500: e.target.checked })
}
disabled={disabled}
className="w-5 h-5 rounded accent-yellow-500"
className="w-5 h-5 rounded accent-nofx-gold"
/>
<span style={{ color: '#EAECEF' }}>{t('useAI500')}</span>
<span className="text-nofx-text">{t('useAI500')}</span>
</label>
{config.use_ai500 && (
<div className="flex items-center gap-3 pl-8">
<span className="text-sm" style={{ color: '#848E9C' }}>
<span className="text-sm text-nofx-text-muted">
{t('ai500Limit')}:
</span>
<select
@@ -355,12 +324,7 @@ export function CoinSourceEditor({
onChange({ ...config, ai500_limit: parseInt(e.target.value) || 10 })
}
disabled={disabled}
className="px-3 py-1.5 rounded"
style={{
background: '#0B0E11',
border: '1px solid #2B3139',
color: '#EAECEF',
}}
className="px-3 py-1.5 rounded bg-nofx-bg border border-nofx-gold/20 text-nofx-text"
>
{[5, 10, 15, 20, 30, 50].map(n => (
<option key={n} value={n}>{n}</option>
@@ -369,7 +333,7 @@ export function CoinSourceEditor({
</div>
)}
<p className="text-xs pl-8" style={{ color: '#5E6673' }}>
<p className="text-xs pl-8 text-nofx-text-muted">
{t('nofxosNote')}
</p>
</div>
@@ -379,16 +343,12 @@ export function CoinSourceEditor({
{/* OI Top Options */}
{(config.source_type === 'oi_top' || config.source_type === 'mixed') && (
<div
className="p-4 rounded-lg"
style={{
background: 'rgba(14, 203, 129, 0.05)',
border: '1px solid rgba(14, 203, 129, 0.2)',
}}
className="p-4 rounded-lg bg-nofx-success/5 border border-nofx-success/20"
>
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<TrendingUp className="w-4 h-4" style={{ color: '#0ECB81' }} />
<span className="text-sm font-medium" style={{ color: '#EAECEF' }}>
<TrendingUp className="w-4 h-4 text-nofx-success" />
<span className="text-sm font-medium text-nofx-text">
OI Top {t('dataSourceConfig')}
</span>
<NofxOSBadge />
@@ -404,14 +364,14 @@ export function CoinSourceEditor({
!disabled && onChange({ ...config, use_oi_top: e.target.checked })
}
disabled={disabled}
className="w-5 h-5 rounded accent-green-500"
className="w-5 h-5 rounded accent-nofx-success"
/>
<span style={{ color: '#EAECEF' }}>{t('useOITop')}</span>
<span className="text-nofx-text">{t('useOITop')}</span>
</label>
{config.use_oi_top && (
<div className="flex items-center gap-3 pl-8">
<span className="text-sm" style={{ color: '#848E9C' }}>
<span className="text-sm text-nofx-text-muted">
{t('oiTopLimit')}:
</span>
<select
@@ -421,12 +381,7 @@ export function CoinSourceEditor({
onChange({ ...config, oi_top_limit: parseInt(e.target.value) || 20 })
}
disabled={disabled}
className="px-3 py-1.5 rounded"
style={{
background: '#0B0E11',
border: '1px solid #2B3139',
color: '#EAECEF',
}}
className="px-3 py-1.5 rounded bg-nofx-bg border border-nofx-gold/20 text-nofx-text"
>
{[5, 10, 15, 20, 30, 50].map(n => (
<option key={n} value={n}>{n}</option>
@@ -435,7 +390,7 @@ export function CoinSourceEditor({
</div>
)}
<p className="text-xs pl-8" style={{ color: '#5E6673' }}>
<p className="text-xs pl-8 text-nofx-text-muted">
{t('nofxosNote')}
</p>
</div>