diff --git a/api/server.go b/api/server.go index 6b56a848..2f63e7c6 100644 --- a/api/server.go +++ b/api/server.go @@ -14,9 +14,6 @@ import ( "nofx/manager" "nofx/market" "nofx/provider/alpaca" - "nofx/provider/coinank/coinank_api" - "nofx/provider/coinank/coinank_enum" - "nofx/provider/hyperliquid" "nofx/provider/twelvedata" "nofx/store" "nofx/trader" @@ -125,6 +122,7 @@ func (s *Server) setupRoutes() { // Market data (no authentication required) api.GET("/klines", s.handleKlines) + api.GET("/klines", s.handleKlines) api.GET("/symbols", s.handleSymbols) // Authentication related routes (no authentication required) @@ -197,7 +195,7 @@ func (s *Server) setupRoutes() { protected.GET("/positions", s.handlePositions) protected.GET("/positions/history", s.handlePositionHistory) protected.GET("/trades", s.handleTrades) - protected.GET("/orders", s.handleOrders) // Order list (all orders) + protected.GET("/orders", s.handleOrders) // Order list (all orders) protected.GET("/orders/:id/fills", s.handleOrderFills) // Order fill details protected.GET("/decisions", s.handleDecisions) protected.GET("/decisions/latest", s.handleLatestDecisions) @@ -2175,9 +2173,9 @@ func (s *Server) handlePositionHistory(c *gin.Context) { directionStats, _ := store.Position().GetDirectionStats(trader.GetID()) c.JSON(http.StatusOK, gin.H{ - "positions": positions, - "stats": stats, - "symbol_stats": symbolStats, + "positions": positions, + "stats": stats, + "symbol_stats": symbolStats, "direction_stats": directionStats, }) } @@ -2556,8 +2554,8 @@ func (s *Server) getKlinesFromAlpaca(symbol, interval string, limit int) ([]mark High: bar.High, Low: bar.Low, Close: bar.Close, - Volume: float64(bar.Volume), // 股数 - QuoteVolume: float64(bar.Volume) * bar.Close, // 成交额 = 股数 * 收盘价 (USD) + Volume: float64(bar.Volume), // 股数 + QuoteVolume: float64(bar.Volume) * bar.Close, // 成交额 = 股数 * 收盘价 (USD) CloseTime: bar.Timestamp.UnixMilli(), } } @@ -2638,8 +2636,8 @@ func (s *Server) getKlinesFromHyperliquid(symbol, interval string, limit int) ([ High: high, Low: low, Close: close, - Volume: volume, // 合约数量 - QuoteVolume: volume * close, // 成交额 (USD) + Volume: volume, // 合约数量 + QuoteVolume: volume * close, // 成交额 (USD) CloseTime: candle.CloseTime, } } diff --git a/docker-compose.yml b/docker-compose.yml index b17bbab2..bebeef92 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,6 +9,7 @@ services: stop_grace_period: 30s # 允许应用有 30 秒时间优雅关闭 ports: - "${NOFX_BACKEND_PORT:-8080}:8080" + - "6060:6060" # pprof profiling volumes: - ./data:/app/data - /etc/localtime:/etc/localtime:ro diff --git a/web/src/components/landing/core/AgentGrid.tsx b/web/src/components/landing/core/AgentGrid.tsx index 248f1939..33f51ff8 100644 --- a/web/src/components/landing/core/AgentGrid.tsx +++ b/web/src/components/landing/core/AgentGrid.tsx @@ -1,83 +1,126 @@ import { motion } from 'framer-motion' -import { Bot, TrendingUp, Layers } from 'lucide-react' +import { Bot, TrendingUp, Layers, Zap, Hexagon, Crosshair } from 'lucide-react' const agents = [ - { name: "Alpha-1", type: "Scalper", apy: "142%", winRate: "68%", exposure: "Low", avatar: "/images/nofx_mascot.png", color: "text-nofx-gold" }, - { name: "Beta-X", type: "Swing", apy: "89%", winRate: "55%", exposure: "Med", icon: TrendingUp, color: "text-blue-400" }, - { name: "Gamma-Ray", type: "Arbitrage", apy: "24%", winRate: "99%", exposure: "Zero", icon: Layers, color: "text-purple-400" }, + { + name: "ALPHA-1", + class: "SCALPER", + desc: "High-frequency microstructure exploitation.", + apy: "142%", + winRate: "68%", + risk: "HIGH", + color: "text-nofx-gold", + border: "border-nofx-gold/50", + bg_glow: "shadow-[0_0_30px_rgba(240,185,11,0.1)]", + icon: Zap + }, + { + name: "BETA-X", + class: "SWING_OPS", + desc: "Multi-day trend extraction engine.", + apy: "89%", + winRate: "55%", + risk: "MED", + color: "text-blue-400", + border: "border-blue-400/30", + bg_glow: "shadow-[0_0_30px_rgba(96,165,250,0.1)]", + icon: TrendingUp + }, + { + name: "GAMMA-RAY", + class: "ARBITRAGE", + desc: "Risk-free spatial price equalization.", + apy: "24%", + winRate: "99%", + risk: "ZERO", + color: "text-purple-400", + border: "border-purple-400/30", + bg_glow: "shadow-[0_0_30px_rgba(192,132,252,0.1)]", + icon: Layers + }, ] export default function AgentGrid() { return ( -
-
+
-
-
-

- Deployable Agents -

+ {/* Background Details */} +
+ +
+ +
+ +
+
+
+ Operator Select +
+

+ Available Units +

+
+
+ SELECT AN AUTONOMOUS AGENT TO BEGIN DEPLOYMENT. UNITS ARE PRE-TRAINED ON HISTORICAL TICKS. +
-
+
{agents.map((agent, i) => { const Icon = agent.icon return ( - {/* Header */} -
-
-
{agent.type} CLASS
-
- {agent.name} - {i === 0 && TOP RATED} + {/* Top "Hinge" decoration */} +
+ +
+ {/* Header */} +
+
+ +
+
+
Class
+
{agent.class}
- + + {/* Name & Desc */} +

{agent.name}

+

{agent.desc}

+ + {/* Stats Grid */} +
+
+
APY
+
{agent.apy}
+
+
+
Win %
+
{agent.winRate}
+
+
+
Risk
+
{agent.risk}
+
+
+ + {/* Action Btn */} +
- {/* Stats Grid */} -
-
-
APY
-
{agent.apy}
-
-
-
Win Rate
-
{agent.winRate}
-
-
-
Risk
-
{agent.exposure}
-
-
- - {/* Visual Asset (Avatar or Abstract Icon) */} -
- {agent.avatar ? ( - Agent - ) : ( - Icon && - )} -
- - {/* Action */} - + {/* Decorative Background Elements */} +
+
) diff --git a/web/src/components/landing/core/LiveFeed.tsx b/web/src/components/landing/core/LiveFeed.tsx index 0daf8132..3d4e2e29 100644 --- a/web/src/components/landing/core/LiveFeed.tsx +++ b/web/src/components/landing/core/LiveFeed.tsx @@ -1,63 +1,112 @@ import { motion } from 'framer-motion' -import { Activity, BarChart3, Globe } from 'lucide-react' +import { Activity, BarChart3, Globe, Wifi, Server, Database, Lock } from 'lucide-react' +import { useState, useEffect } from 'react' -// Mock Data for "Live" Feed -const logs = [ - { time: "14:02:23", type: "EXE", msg: "Bot-Alpha executed BUY BTC-USDT @ 64230.50", color: "text-green-500" }, - { time: "14:02:24", type: "SIG", msg: "High vol detected in ETH-PERP. Signal strength: 0.89", color: "text-nofx-gold" }, - { time: "14:02:25", type: "NET", msg: "Block propagation delay < 2ms", color: "text-zinc-500" }, - { time: "14:02:27", type: "EXE", msg: "Bot-Beta executed SELL SOL-USDT @ 145.20", color: "text-red-500" }, - { time: "14:02:28", type: "SYS", msg: "Memory pool optimization complete.", color: "text-nofx-accent" }, - { time: "14:02:30", type: "ARB", msg: "Arbitrage opportunity found: BINANCE vs BYBIT (0.4%)", color: "text-blue-400" }, -] +const generateLog = (id) => { + const types = ['EXE', 'ARB', 'LIQ', 'NET', 'SYS'] + const pairs = ['BTC-USDT', 'ETH-PERP', 'SOL-USDT', 'BNB-BUSD'] + const actions = ['BUY', 'SELL', 'SHORT', 'LONG'] + const type = types[Math.floor(Math.random() * types.length)] + + let msg = '' + let color = '' + + switch (type) { + case 'EXE': + msg = `BOT-${Math.floor(Math.random() * 99)} ${actions[Math.floor(Math.random() * 4)]} ${pairs[Math.floor(Math.random() * 4)]} @ ${Math.floor(Math.random() * 60000)}` + color = 'text-green-500' + break; + case 'ARB': + msg = `Spread detected: BINANCE <> BYBIT (${(Math.random()).toFixed(3)}%)` + color = 'text-nofx-gold' + break; + case 'LIQ': + msg = `Liquidation Alert: ${pairs[Math.floor(Math.random() * 4)]} $${Math.floor(Math.random() * 100)}k REKT` + color = 'text-red-500' + break; + case 'NET': + msg = `Block propagation latency < ${Math.floor(Math.random() * 10)}ms` + color = 'text-zinc-500' + break; + default: + msg = `System optimization cycle complete. Allocating resources.` + color = 'text-blue-400' + } + + return { id, time: new Date().toLocaleTimeString('en-US', { hour12: false }) + '.' + Math.floor(Math.random() * 999), type, msg, color } +} export default function LiveFeed() { - return ( -
-
+ const [logs, setLogs] = useState([]) - {/* Left Status Panel */} -
-
- -
-
SYSTEM LOAD
-
42%
-
+ useEffect(() => { + // Initial population + const initialLogs = Array.from({ length: 8 }).map((_, i) => generateLog(i)) + setLogs(initialLogs) + + const interval = setInterval(() => { + setLogs(prev => { + const newLog = generateLog(Date.now()) + return [newLog, ...prev.slice(0, 7)] + }) + }, 800) // Fast 800ms updates for HFT feel + + return () => clearInterval(interval) + }, []) + + return ( +
+
+ +
+ + {/* Left Status Bar (Static) */} +
+
+
+ WS_CONN: STABLE
-
- -
-
ACTIVE NODES
-
8,249
-
-
-
- -
-
24H VOL
-
$4.2B
-
+
+ TPS: 48,291
- {/* Right Scrolling Log */} -
-
- {logs.map((log, i) => ( + {/* Right Scrolling Log - Vertical on mobile, Single line ticker on Desktop */} +
+ + {/* Desktop View: Single Line Fade */} +
+ {logs.slice(0, 1).map((log) => ( [{log.time}] - {log.type} - {log.msg} + {log.type} + {log.msg} ))}
+ + {/* Mobile View: Vertical Stack */} +
+ {logs.map((log) => ( +
+ {log.time.split('.')[0]} + {log.type} + {log.msg} +
+ ))} +
+
diff --git a/web/src/components/landing/core/TerminalHero.tsx b/web/src/components/landing/core/TerminalHero.tsx index ce106948..df375857 100644 --- a/web/src/components/landing/core/TerminalHero.tsx +++ b/web/src/components/landing/core/TerminalHero.tsx @@ -1,224 +1,324 @@ import { motion } from 'framer-motion' -import { ArrowRight, Terminal as TerminalIcon, Star, GitFork, Users, Activity, Layers, Cpu, Network } from 'lucide-react' +import { ArrowRight, Shield, Activity, CircuitBoard, Cpu, Wifi, Globe, Lock, Zap, Star, GitFork, Users, MessageCircle } from 'lucide-react' import { useState, useEffect } from 'react' -import { OFFICIAL_LINKS } from '../../../constants/branding' +import { httpClient } from '../../../lib/httpClient' +import { useGitHubStats } from '../../../hooks/useGitHubStats' export default function TerminalHero() { - const [text, setText] = useState('') - const [githubData, setGithubData] = useState({ stars: '9.4k', forks: '2.4k', subscribers: '74' }) - const fullText = "INITIALIZING NOFX KERNEL... CRYPTO | STOCKS | FOREX | METALS... SYSTEM READY." + + // Real-time price state + const [prices, setPrices] = useState>({ + BTC: '...', + ETH: '...', + SOL: '...', + BNB: '...', + XRP: '...', + DOGE: '...', + ADA: '...', + AVAX: '...' + }) useEffect(() => { - // Typing effect - let i = 0 - const timer = setInterval(() => { - setText(fullText.slice(0, i)) - i++ - if (i > fullText.length) clearInterval(timer) - }, 30) + const fetchPrices = async () => { + const symbols = ['BTC', 'ETH', 'SOL', 'BNB', 'XRP', 'DOGE', 'ADA', 'AVAX'] - // Fetch GitHub Data - fetch('https://api.github.com/repos/NoFxAiOS/nofx') - .then(res => res.json()) - .then(data => { - if (data.stargazers_count) { - setGithubData({ - stars: (data.stargazers_count / 1000).toFixed(1) + 'k', - forks: (data.forks_count / 1000).toFixed(1) + 'k', - subscribers: data.subscribers_count?.toString() || '74' - }) - } - }) - .catch(err => console.error("Failed to fetch GitHub stats", err)) + // We use Promise.all to fetch them in parallel for now, or sequentially if rate limited. + // Parallel is better for UI responsiveness. + try { + const results = await Promise.all(symbols.map(async (sym) => { + try { + const res = await httpClient.get(`/api/klines?symbol=${sym}USDT&interval=1m&limit=1`) + if (res.success && res.data?.length > 0) { + const closePrice = parseFloat(res.data[0].close) + // Format price: < 1 use 4 decimals, > 1 use 2 + const formatted = closePrice < 1 + ? closePrice.toFixed(4) + : closePrice.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + return { symbol: sym, price: formatted } + } + } catch (err) { + // ignore individual failures + } + return null + })) - return () => clearInterval(timer) + const newPrices: Record = {} + results.forEach(r => { + if (r) newPrices[r.symbol] = r.price + }) + + setPrices(prev => ({ ...prev, ...newPrices })) + + } catch (e) { + console.error("Failed to fetch market prices", e) + } + } + + fetchPrices() + const interval = setInterval(fetchPrices, 5000) + return () => clearInterval(interval) }, []) return ( -
+
- {/* 1. ARCHITECTURAL BACKGROUND / HOLOGRAPHIC CONSTRUCT */} -
+ {/* BACKGROUND LAYERS */} + {/* 1. Grid */} +
+
- {/* The Mascot "Ghost" in the Machine - PREMIUM & CLEAN */} -
-
- + {/* 2. World Map / Data Viz Background (Abstract) */} +
+
+
+
- {/* Clean Horizontal Scanline Overlay */} -
+ {/* 3. Gradient Spots */} +
+
- {/* Subtle Glow Behind */} -
+ {/* CONTENT GRID */} +
+ + {/* LEFT COLUMN: TELEMETRY & STATUS */} +
+ + {/* Top: System Health */} +
+
+

+ SYSTEM_DIAGNOSTICS +

+
+
+ KERNEL_LATENCY + 12ms +
+
+
+
+ +
+ MEMORY_INTEGRITY + 100% +
+
+
+
+ +
+ UPTIME + 99.999% +
+
+
+ +
+
+ + SECURITY PROTOCOLS +
+
+
+
+
+
+
+
LEVEL 3 ACTIVATE
+
+
+ + {/* Bottom: Network Log */} +
+
> CONNECTING TO MAINNET... OK
+
> SYNCING NODES (424/424)... OK
+
> LOADING ASSETS... DONE
+
> AWAITING USER INPUT_
- {/* Clean Geometric Grid */} - - - - - - - - -
+ {/* CENTER COLUMN: MAIN ACTION */} +
-
+ {/* Project Identity Chip */} + + + + + + NOFX OPEN-SOURCE AGENTIC OS + -
+ {/* Main Title - Massive & Impactful */} +

+ AGENTIC
+ TRADING +

- {/* LEFT COLUMN: Main System Interface */} -
+

+ The World's First Open-Source Agentic Trading OS. + Deploy autonomous high-frequency trading agents powered by advanced LLMs. +

- {/* System Status Tag */} - document.getElementById('market-scanner')?.scrollIntoView({ behavior: 'smooth' })}> + + ~ + deploy agent --strategy=hft + +
+ + {/* CTA Buttons */} +
+ +
- {/* Main Headline with Project Specifics */} - -

- AGENTIC
- TRADING OS -

+ {/* Community Stats Row */} + - {/* SVG Connector Line */} -
-
-
-
- +
+
- {/* Typing Terminal Output */} -
-
- > {text} -
+ {/* RIGHT COLUMN: HOLOGRAPHIC DISPLAY - Absolute Overlay for "Far Right" Effect */} +
+
+ {/* 3D Hologram Effect Container */} +
- {/* Clean Markets Row */} -
- CRYPTO - STOCKS - FOREX - METALS -
-
+ {/* Scanning Grid behind Mascot */} +
- {/* Primary Actions */} -
- - - - SOURCE CODE - - + {/* The Mascot Image with Glitch/Holo Effects */} +
+
+ Agent NoFX + {/* Holo Scan Line */} +
- {/* RIGHT COLUMN: Modules & Data HUD */} -
+ {/* Floating Data Widgets around Hologram */} + + + - {/* Module 1: GitHub Intelligence */} -
-
- -
-
- COMMUNITY UPLINK -
-
-
-
-
- -
-
-
- {githubData.stars} -
-
Active Star-gazers
-
-
-
- {githubData.forks} -
-
Protocol Forks
-
-
-
- - {/* Module 2: System Capabilities (Specific to NoFX) */} -
-
ACTIVE MODULES
- -
- STRATEGY STUDIO - READY -
-
- DEBATE ARENA - Running -
-
- BACKTEST LAB - Idle -
-
- -
+ + +
-
- {/* Decorative Footer */} -
-
- -
- NOFX-OS - - 24H VOL: $42.8M - ACTIVE AGENTS: 1,024 -
-
- ENCRYPTED CONNECTION + {/* FLOATING TICKER FOOTER */} +
+
+ GLOBAL MARKET ACCESS + FLASH LOANS ENABLED + LOW LATENCY LINK: 12ms + + {/* Dynamic Coins */} + {Object.entries(prices).map(([symbol, price]) => ( + + {symbol.toUpperCase()}/USDT ${price} + + ))} + + AI MODEL: GEMINI-PRO-1.5 + + {/* Duplicate sequence for seamless loop effect (basic set) */} + {Object.entries(prices).map(([symbol, price]) => ( + + {symbol.toUpperCase()}/USDT ${price} + + ))}
-
+ + {/* CRT OVERLAY (Global) */} +
+
+ ) +} + +import { OFFICIAL_LINKS } from '../../../constants/branding' + +function CommunityStats() { + const { stars, forks, contributors, isLoading, error } = useGitHubStats('tinkle-community', 'nofx') + + const stats = [ + { + label: 'GITHUB STARS', + value: isLoading ? '...' : (error ? '1.2k+' : stars.toLocaleString()), + icon: Star, + color: 'text-yellow-400', + href: OFFICIAL_LINKS.github + }, + { + label: 'FORKS', + value: isLoading ? '...' : (error ? '240+' : forks.toLocaleString()), + icon: GitFork, + color: 'text-blue-400', + href: `${OFFICIAL_LINKS.github}/fork` + }, + { + label: 'CONTRIBUTORS', + value: isLoading ? '...' : (contributors > 0 ? contributors : '50+'), + icon: Users, + color: 'text-green-400', + href: `${OFFICIAL_LINKS.github}/graphs/contributors` + }, + { + label: 'DEV COMMUNITY', + value: '5,800+', // Hardcoded as per user request + icon: MessageCircle, + color: 'text-blue-500', + href: OFFICIAL_LINKS.telegram + } + ] + + return ( +
+ {stats.map((stat, i) => ( + +
+ + {stat.label} +
+ {stat.value} +
+ ))} +
) } diff --git a/web/src/hooks/useGitHubStats.ts b/web/src/hooks/useGitHubStats.ts index c526e03d..e48d6a88 100644 --- a/web/src/hooks/useGitHubStats.ts +++ b/web/src/hooks/useGitHubStats.ts @@ -3,6 +3,7 @@ import { useState, useEffect } from 'react' interface GitHubStats { stars: number forks: number + contributors: number createdAt: string daysOld: number isLoading: boolean @@ -13,6 +14,7 @@ export function useGitHubStats(owner: string, repo: string): GitHubStats { const [stats, setStats] = useState({ stars: 0, forks: 0, + contributors: 0, createdAt: '', daysOld: 0, isLoading: true, @@ -22,26 +24,52 @@ export function useGitHubStats(owner: string, repo: string): GitHubStats { useEffect(() => { const fetchGitHubStats = async () => { try { - const response = await fetch( - `https://api.github.com/repos/${owner}/${repo}` - ) + // Fetch basic repo info + const repoRes = await fetch(`https://api.github.com/repos/${owner}/${repo}`) + if (!repoRes.ok) throw new Error('Failed to fetch GitHub stats') + const repoData = await repoRes.json() - if (!response.ok) { - throw new Error('Failed to fetch GitHub stats') + // Fetch contributors count (using Link header trick for large numbers, or length for small) + // Since we can't easily parse Link header in client-side without exposing logic, + // we'll try a rough count or just a list length valid for first page (max 30 or 100). + // For a more accurate count without pagination, we often check the 'Link' header of: + // https://api.github.com/repos/{owner}/{repo}/contributors?per_page=1&anon=true + let contributorsCount = 0 + try { + const contribRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/contributors?per_page=1&anon=true`) + const linkHeader = contribRes.headers.get('Link') + if (linkHeader) { + const match = linkHeader.match(/page=(\d+)>; rel="last"/) + if (match) { + contributorsCount = parseInt(match[1]) + } + } + // If no link header, it means 1 page. + if (contributorsCount === 0 && contribRes.ok) { + // Fetch list to count (default page size 30) + // actually per_page=1 returns 1. + // We should fetch with per_page=100 to get exact count if <100. + const listRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/contributors?per_page=100&anon=true`) + if (listRes.ok) { + const list = await listRes.json() + contributorsCount = list.length + } + } + } catch (e) { + console.warn('Failed to fetch contributors:', e) } - const data = await response.json() - // Calculate days since creation - const createdDate = new Date(data.created_at) + const createdDate = new Date(repoData.created_at) const now = new Date() const diffTime = Math.abs(now.getTime() - createdDate.getTime()) const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) setStats({ - stars: data.stargazers_count, - forks: data.forks_count, - createdAt: data.created_at, + stars: repoData.stargazers_count, + forks: repoData.forks_count, + contributors: contributorsCount > 0 ? contributorsCount : 0, // Fallback + createdAt: repoData.created_at, daysOld: diffDays, isLoading: false, error: null, diff --git a/web/src/index.css b/web/src/index.css index 443c5dbe..c39ba5b3 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -13,21 +13,27 @@ html { :root { /* NoFX Neo-Gold Design System */ --nofx-gold: #F0B90B; - --nofx-bg: #0B0E11; + --nofx-bg: #05070A; --nofx-accent: #00F0FF; - --nofx-glass: rgba(30, 35, 41, 0.6); + --nofx-glass: rgba(5, 7, 10, 0.7); --nofx-border: rgba(240, 185, 11, 0.2); - --background: #0B0E11; - --header-bg: rgba(11, 14, 17, 0.9); - /* Glass header */ - --glass-bg: rgba(11, 14, 17, 0.6); - --glass-border: rgba(240, 185, 11, 0.1); + /* Cinematic Glows */ + --glow-primary: 0 0 20px rgba(240, 185, 11, 0.3); + --glow-accent: 0 0 20px rgba(0, 240, 255, 0.3); + --glow-text: 0 0 10px rgba(240, 185, 11, 0.5); - --panel-bg: #15181D; - --panel-bg-hover: #1E2329; - --panel-border: rgba(255, 255, 255, 0.08); - --panel-border-hover: rgba(240, 185, 11, 0.4); + --background: #05070A; + --header-bg: rgba(2, 3, 4, 0.85); + /* Deep Abyssal */ + + --glass-bg: rgba(5, 7, 10, 0.4); + --glass-border: rgba(255, 255, 255, 0.08); + + --panel-bg: rgba(14, 18, 23, 0.6); + --panel-bg-hover: rgba(20, 24, 29, 0.8); + --panel-border: rgba(255, 255, 255, 0.1); + --panel-border-hover: rgba(240, 185, 11, 0.5); --foreground: #EAECEF; --text-primary: #EAECEF; @@ -42,19 +48,15 @@ html { --binance-red: #F6465D; --binance-red-bg: rgba(246, 70, 93, 0.12); --binance-red-border: rgba(246, 70, 93, 0.3); + --binance-yellow: #F0B90B; /* Shadows */ --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(240, 185, 11, 0.2); + --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4); + --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.6); + --shadow-glow: 0 0 30px rgba(240, 185, 11, 0.15); - font-family: - 'Inter', - -apple-system, - BlinkMacSystemFont, - 'Segoe UI', - sans-serif; + font-family: 'IBM Plex Mono', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; line-height: 1.6; font-weight: 400; color-scheme: dark; @@ -64,8 +66,69 @@ html { text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - font-feature-settings: 'tnum'; - font-variant-numeric: tabular-nums; +} + +/* CRT & Tech Effects */ +.crt-overlay { + background: + linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.1) 50%), + linear-gradient(90deg, rgba(255, 0, 0, 0.03), rgba(0, 255, 0, 0.01), rgba(0, 0, 255, 0.03)); + background-size: 100% 2px, 3px 100%; + pointer-events: none; +} + +.tech-border { + position: relative; + background: rgba(5, 7, 10, 0.6); + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.tech-border::before, +.tech-border::after { + content: ''; + position: absolute; + width: 8px; + height: 8px; + border: 1px solid var(--nofx-gold); + transition: all 0.3s ease; +} + +.tech-border::before { + top: -1px; + left: -1px; + border-right: none; + border-bottom: none; +} + +.tech-border::after { + bottom: -1px; + right: -1px; + border-left: none; + border-top: none; +} + +.tech-border:hover::before, +.tech-border:hover::after { + width: 100%; + height: 100%; + opacity: 0.5; +} + +.bg-vignette { + background: radial-gradient(circle at center, transparent 0%, rgba(0, 0, 0, 0.6) 80%, #000000 100%); + pointer-events: none; +} + +.text-glow { + text-shadow: 0 0 10px rgba(240, 185, 11, 0.6); +} + +.text-glow-accent { + text-shadow: 0 0 10px rgba(0, 240, 255, 0.6); +} + +.border-glow { + box-shadow: 0 0 15px rgba(240, 185, 11, 0.2); } body { @@ -73,7 +136,7 @@ body { min-width: 320px; min-height: 100vh; background-color: var(--background); - background-image: none; + background-image: radial-gradient(circle at 50% 0%, #151921 0%, #05070a 60%); background-attachment: fixed; } diff --git a/web/tailwind.config.js b/web/tailwind.config.js index 851008d2..b88fce1d 100644 --- a/web/tailwind.config.js +++ b/web/tailwind.config.js @@ -7,11 +7,68 @@ export default { theme: { extend: { colors: { - 'nofx-gold': '#F0B90B', - 'nofx-gold-dim': 'rgba(240, 185, 11, 0.15)', - 'nofx-bg': '#0B0E11', - 'nofx-accent': '#00F0FF', - 'nofx-text': '#EAECEF', + 'nofx-gold': { + DEFAULT: '#F0B90B', + dim: 'rgba(240, 185, 11, 0.1)', + glow: 'rgba(240, 185, 11, 0.5)', + highlight: '#FFD700', + }, + 'nofx-bg': { + DEFAULT: '#05070A', // Deep Void + deeper: '#020304', // Abyssal + lighter: '#0E1217', // Surface + }, + 'nofx-accent': '#00F0FF', // Cyan Cyber + 'nofx-text': { + DEFAULT: '#EAECEF', + muted: '#848E9C', + }, + 'nofx-success': '#0ECB81', + 'nofx-danger': '#F6465D', + }, + fontFamily: { + sans: ['Inter', 'ui-sans-serif', 'system-ui'], + mono: ['JetBrains Mono', 'Menlo', 'Monaco', 'Courier New', 'monospace'], + }, + backgroundImage: { + 'gradient-radial': 'radial-gradient(circle at center, var(--tw-gradient-stops))', + 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', + 'scanlines': "url(\"data:image/svg+xml,%3Csvg width='4' height='4' viewBox='0 0 4 4' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0H4V2H0V0Z' fill='rgba(0,0,0,0.4)'/%3E%3C/svg%3E\")", + 'grid-pattern': "linear-gradient(to right, #1f2937 1px, transparent 1px), linear-gradient(to bottom, #1f2937 1px, transparent 1px)", + }, + animation: { + 'pulse-slow': 'pulse 4s cubic-bezier(0.4, 0, 0.6, 1) infinite', + 'scan': 'scan 8s linear infinite', + 'scan-fast': 'scan 2s linear infinite', + 'float': 'float 6s ease-in-out infinite', + 'glitch': 'glitch 0.3s cubic-bezier(.25, .46, .45, .94) both infinite', + 'shimmer': 'shimmer 2s linear infinite', + }, + keyframes: { + scan: { + '0%': { backgroundPosition: '0 0' }, + '100%': { backgroundPosition: '0 100%' }, + }, + float: { + '0%, 100%': { transform: 'translateY(0)' }, + '50%': { transform: 'translateY(-10px)' }, + }, + glitch: { + '0%': { transform: 'translate(0)' }, + '20%': { transform: 'translate(-2px, 2px)' }, + '40%': { transform: 'translate(-2px, -2px)' }, + '60%': { transform: 'translate(2px, 2px)' }, + '80%': { transform: 'translate(2px, -2px)' }, + '100%': { transform: 'translate(0)' }, + }, + shimmer: { + '0%': { backgroundPosition: '-200% 0' }, + '100%': { backgroundPosition: '200% 0' }, + }, + }, + boxShadow: { + 'neon': '0 0 5px theme("colors.nofx-gold.DEFAULT"), 0 0 20px theme("colors.nofx-gold.dim")', + 'neon-blue': '0 0 5px theme("colors.nofx-accent"), 0 0 20px rgba(0, 240, 255, 0.2)', }, }, },