mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-18 01:44:38 +08:00
feat: improve landing page UI and add pprof port mapping
This commit is contained in:
@@ -14,9 +14,6 @@ import (
|
|||||||
"nofx/manager"
|
"nofx/manager"
|
||||||
"nofx/market"
|
"nofx/market"
|
||||||
"nofx/provider/alpaca"
|
"nofx/provider/alpaca"
|
||||||
"nofx/provider/coinank/coinank_api"
|
|
||||||
"nofx/provider/coinank/coinank_enum"
|
|
||||||
"nofx/provider/hyperliquid"
|
|
||||||
"nofx/provider/twelvedata"
|
"nofx/provider/twelvedata"
|
||||||
"nofx/store"
|
"nofx/store"
|
||||||
"nofx/trader"
|
"nofx/trader"
|
||||||
@@ -125,6 +122,7 @@ func (s *Server) setupRoutes() {
|
|||||||
|
|
||||||
// Market data (no authentication required)
|
// Market data (no authentication required)
|
||||||
api.GET("/klines", s.handleKlines)
|
api.GET("/klines", s.handleKlines)
|
||||||
|
api.GET("/klines", s.handleKlines)
|
||||||
api.GET("/symbols", s.handleSymbols)
|
api.GET("/symbols", s.handleSymbols)
|
||||||
|
|
||||||
// Authentication related routes (no authentication required)
|
// Authentication related routes (no authentication required)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ services:
|
|||||||
stop_grace_period: 30s # 允许应用有 30 秒时间优雅关闭
|
stop_grace_period: 30s # 允许应用有 30 秒时间优雅关闭
|
||||||
ports:
|
ports:
|
||||||
- "${NOFX_BACKEND_PORT:-8080}:8080"
|
- "${NOFX_BACKEND_PORT:-8080}:8080"
|
||||||
|
- "6060:6060" # pprof profiling
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/app/data
|
- ./data:/app/data
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
|||||||
@@ -1,83 +1,126 @@
|
|||||||
import { motion } from 'framer-motion'
|
import { motion } from 'framer-motion'
|
||||||
import { Bot, TrendingUp, Layers } from 'lucide-react'
|
import { Bot, TrendingUp, Layers, Zap, Hexagon, Crosshair } from 'lucide-react'
|
||||||
|
|
||||||
const agents = [
|
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: "ALPHA-1",
|
||||||
{ name: "Gamma-Ray", type: "Arbitrage", apy: "24%", winRate: "99%", exposure: "Zero", icon: Layers, color: "text-purple-400" },
|
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() {
|
export default function AgentGrid() {
|
||||||
return (
|
return (
|
||||||
<section id="market-scanner" className="py-24 bg-nofx-bg relative">
|
<section id="market-scanner" className="py-24 bg-nofx-bg relative overflow-hidden">
|
||||||
<div className="max-w-7xl mx-auto px-6">
|
|
||||||
|
|
||||||
<div className="flex items-center gap-4 mb-12">
|
{/* Background Details */}
|
||||||
<div className="w-2 h-8 bg-nofx-gold" />
|
<div className="absolute top-0 right-0 p-10 opacity-20 pointer-events-none">
|
||||||
<h2 className="text-3xl font-black text-white uppercase tracking-tighter">
|
<Hexagon className="w-64 h-64 text-zinc-800" strokeWidth={0.5} />
|
||||||
Deployable <span className="text-nofx-gold">Agents</span>
|
|
||||||
</h2>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
<div className="max-w-7xl mx-auto px-6 relative z-10">
|
||||||
|
|
||||||
|
<div className="flex flex-col md:flex-row justify-between items-end mb-16 gap-6">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2 text-nofx-gold font-mono text-xs mb-2 tracking-widest uppercase">
|
||||||
|
<Crosshair className="w-4 h-4" /> Operator Select
|
||||||
|
</div>
|
||||||
|
<h2 className="text-4xl md:text-5xl font-black text-white uppercase tracking-tighter">
|
||||||
|
Available <span className="text-transparent bg-clip-text bg-gradient-to-r from-nofx-gold to-white">Units</span>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div className="font-mono text-right text-xs text-zinc-500 max-w-xs">
|
||||||
|
SELECT AN AUTONOMOUS AGENT TO BEGIN DEPLOYMENT. UNITS ARE PRE-TRAINED ON HISTORICAL TICKS.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
{agents.map((agent, i) => {
|
{agents.map((agent, i) => {
|
||||||
const Icon = agent.icon
|
const Icon = agent.icon
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={i}
|
key={i}
|
||||||
initial={{ opacity: 0, scale: 0.95 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
whileInView={{ opacity: 1, scale: 1 }}
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
transition={{ delay: i * 0.1 }}
|
transition={{ delay: i * 0.1 }}
|
||||||
className={`relative bg-zinc-900/50 border border-zinc-800 p-6 overflow-hidden hover:border-zinc-600 transition-colors group ${i === 0 ? 'border-nofx-gold/50 shadow-[0_0_30px_rgba(240,185,11,0.1)]' : ''}`}
|
className={`group relative bg-black/40 backdrop-blur-sm border ${agent.border} overflow-hidden hover:bg-zinc-900/40 transition-all duration-300 ${agent.bg_glow}`}
|
||||||
|
style={{ clipPath: 'polygon(0 0, 100% 0, 100% 85%, 85% 100%, 0 100%)' }}
|
||||||
>
|
>
|
||||||
|
{/* Top "Hinge" decoration */}
|
||||||
|
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-transparent via-white/10 to-transparent"></div>
|
||||||
|
|
||||||
|
<div className="p-8 relative z-10">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex justify-between items-start mb-6">
|
<div className="flex justify-between items-start mb-6">
|
||||||
<div>
|
<div className="p-3 bg-zinc-900/80 rounded border border-zinc-700">
|
||||||
<div className="text-zinc-400 text-xs font-mono uppercase mb-1">{agent.type} CLASS</div>
|
<Icon className={`w-8 h-8 ${agent.color}`} />
|
||||||
<div className="text-2xl font-bold text-white flex items-center gap-2">
|
</div>
|
||||||
{agent.name}
|
<div className="text-right">
|
||||||
{i === 0 && <span className="text-[10px] bg-nofx-gold text-black px-1.5 py-0.5 rounded font-bold">TOP RATED</span>}
|
<div className="text-[10px] font-mono text-zinc-500 uppercase">Class</div>
|
||||||
|
<div className={`font-bold font-mono tracking-wider ${agent.color}`}>{agent.class}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Bot className={`w-8 h-8 ${agent.color}`} />
|
|
||||||
</div>
|
{/* Name & Desc */}
|
||||||
|
<h3 className="text-3xl font-bold text-white mb-2 tracking-tight group-hover:text-nofx-accent transition-colors">{agent.name}</h3>
|
||||||
|
<p className="text-zinc-500 text-sm mb-8 leading-relaxed h-10">{agent.desc}</p>
|
||||||
|
|
||||||
{/* Stats Grid */}
|
{/* Stats Grid */}
|
||||||
<div className="grid grid-cols-3 gap-2 mb-6 font-mono text-sm">
|
<div className="grid grid-cols-3 gap-px bg-zinc-800/50 border border-zinc-800 rounded overflow-hidden mb-8">
|
||||||
<div className="bg-black/40 p-2 rounded border border-zinc-800 group-hover:border-zinc-700 transition-colors">
|
<div className="bg-black/60 p-3 text-center group-hover:bg-zinc-900/60 transition-colors">
|
||||||
<div className="text-zinc-500 text-[10px] uppercase">APY</div>
|
<div className="text-[10px] text-zinc-500 uppercase font-mono mb-1">APY</div>
|
||||||
<div className="text-green-400 font-bold">{agent.apy}</div>
|
<div className="text-green-400 font-bold">{agent.apy}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-black/40 p-2 rounded border border-zinc-800 group-hover:border-zinc-700 transition-colors">
|
<div className="bg-black/60 p-3 text-center group-hover:bg-zinc-900/60 transition-colors">
|
||||||
<div className="text-zinc-500 text-[10px] uppercase">Win Rate</div>
|
<div className="text-[10px] text-zinc-500 uppercase font-mono mb-1">Win %</div>
|
||||||
<div className={`font-bold ${agent.color}`}>{agent.winRate}</div>
|
<div className="text-white font-bold">{agent.winRate}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-black/40 p-2 rounded border border-zinc-800 group-hover:border-zinc-700 transition-colors">
|
<div className="bg-black/60 p-3 text-center group-hover:bg-zinc-900/60 transition-colors">
|
||||||
<div className="text-zinc-500 text-[10px] uppercase">Risk</div>
|
<div className="text-[10px] text-zinc-500 uppercase font-mono mb-1">Risk</div>
|
||||||
<div className="text-white font-bold">{agent.exposure}</div>
|
<div className={`${agent.color} font-bold`}>{agent.risk}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Visual Asset (Avatar or Abstract Icon) */}
|
{/* Action Btn */}
|
||||||
<div className="absolute right-[-20px] bottom-[-20px] opacity-10 group-hover:opacity-20 transition-all duration-500 group-hover:scale-110 pointer-events-none">
|
<button className={`w-full py-4 text-xs font-bold font-mono uppercase tracking-[0.2em] border border-zinc-700 hover:border-${agent.color === 'text-nofx-gold' ? 'nofx-gold' : 'white'} hover:bg-white/5 transition-all flex items-center justify-center gap-2 group-hover:text-white`}>
|
||||||
{agent.avatar ? (
|
<span className={agent.color}>[</span> INITIALIZE <span className={agent.color}>]</span>
|
||||||
<img
|
|
||||||
src={agent.avatar}
|
|
||||||
alt="Agent"
|
|
||||||
className="w-40 h-40 object-cover grayscale mix-blend-screen"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
Icon && <Icon strokeWidth={1} className={`w-40 h-40 ${agent.color}`} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Action */}
|
|
||||||
<button className={`w-full py-3 font-bold uppercase tracking-wider text-sm transition-colors ${i === 0
|
|
||||||
? 'bg-nofx-gold text-black hover:bg-white'
|
|
||||||
: 'bg-zinc-800 text-zinc-400 hover:bg-zinc-700'
|
|
||||||
}`}>
|
|
||||||
Initialize Agent
|
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Decorative Background Elements */}
|
||||||
|
<div className="absolute -right-10 -bottom-10 w-40 h-40 bg-gradient-to-br from-white/5 to-transparent rounded-full blur-2xl group-hover:opacity-50 transition-opacity opacity-20"></div>
|
||||||
|
<div className="absolute inset-0 bg-scanlines opacity-20 pointer-events-none"></div>
|
||||||
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,63 +1,112 @@
|
|||||||
import { motion } from 'framer-motion'
|
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 generateLog = (id) => {
|
||||||
const logs = [
|
const types = ['EXE', 'ARB', 'LIQ', 'NET', 'SYS']
|
||||||
{ time: "14:02:23", type: "EXE", msg: "Bot-Alpha executed BUY BTC-USDT @ 64230.50", color: "text-green-500" },
|
const pairs = ['BTC-USDT', 'ETH-PERP', 'SOL-USDT', 'BNB-BUSD']
|
||||||
{ time: "14:02:24", type: "SIG", msg: "High vol detected in ETH-PERP. Signal strength: 0.89", color: "text-nofx-gold" },
|
const actions = ['BUY', 'SELL', 'SHORT', 'LONG']
|
||||||
{ time: "14:02:25", type: "NET", msg: "Block propagation delay < 2ms", color: "text-zinc-500" },
|
const type = types[Math.floor(Math.random() * types.length)]
|
||||||
{ 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" },
|
let msg = ''
|
||||||
{ time: "14:02:30", type: "ARB", msg: "Arbitrage opportunity found: BINANCE vs BYBIT (0.4%)", color: "text-blue-400" },
|
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() {
|
export default function LiveFeed() {
|
||||||
|
const [logs, setLogs] = useState([])
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<section className="w-full bg-black border-y border-zinc-800 py-4 overflow-hidden">
|
<section className="w-full bg-[#020304] border-y border-zinc-800 py-1 overflow-hidden relative">
|
||||||
<div className="max-w-[1920px] mx-auto px-6 flex flex-col md:flex-row gap-6">
|
<div className="absolute inset-0 bg-scanlines opacity-10 pointer-events-none"></div>
|
||||||
|
|
||||||
{/* Left Status Panel */}
|
<div className="max-w-[1920px] mx-auto px-4 flex flex-col md:flex-row gap-0 md:gap-8 items-stretch h-[320px] md:h-12 text-xs font-mono">
|
||||||
<div className="w-full md:w-1/3 flex items-center justify-between md:justify-start gap-8 text-xs font-mono text-zinc-500 border-b md:border-b-0 md:border-r border-zinc-900 pb-4 md:pb-0">
|
|
||||||
<div className="flex items-center gap-3">
|
{/* Left Status Bar (Static) */}
|
||||||
<Activity className="w-4 h-4 text-nofx-gold" />
|
<div className="hidden md:flex items-center gap-6 text-zinc-600 border-r border-zinc-900 pr-6 shrink-0">
|
||||||
<div>
|
<div className="flex items-center gap-2">
|
||||||
<div className="text-zinc-300 font-bold">SYSTEM LOAD</div>
|
<div className="w-1.5 h-1.5 bg-green-500 rounded-full animate-pulse"></div>
|
||||||
<div className="text-nofx-gold">42%</div>
|
<span className="font-bold text-zinc-400">WS_CONN: STABLE</span>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<Globe className="w-4 h-4 text-nofx-accent" />
|
|
||||||
<div>
|
|
||||||
<div className="text-zinc-300 font-bold">ACTIVE NODES</div>
|
|
||||||
<div className="text-nofx-accent">8,249</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<BarChart3 className="w-4 h-4 text-green-500" />
|
|
||||||
<div>
|
|
||||||
<div className="text-zinc-300 font-bold">24H VOL</div>
|
|
||||||
<div className="text-green-500">$4.2B</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-nofx-gold">TPS: 48,291</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right Scrolling Log */}
|
{/* Right Scrolling Log - Vertical on mobile, Single line ticker on Desktop */}
|
||||||
<div className="flex-1 font-mono text-xs md:text-sm h-32 md:h-12 overflow-hidden relative mask-image-b">
|
<div className="flex-1 overflow-hidden relative font-mono text-[10px] md:text-sm h-full flex items-center">
|
||||||
<div className="absolute inset-0 flex flex-col gap-1 animate-slide-up">
|
|
||||||
{logs.map((log, i) => (
|
{/* Desktop View: Single Line Fade */}
|
||||||
|
<div className="hidden md:block w-full h-full relative">
|
||||||
|
{logs.slice(0, 1).map((log) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={i}
|
key={log.id}
|
||||||
initial={{ opacity: 0, x: -10 }}
|
initial={{ opacity: 0, x: -20 }}
|
||||||
animate={{ opacity: 1, x: 0 }}
|
animate={{ opacity: 1, x: 0 }}
|
||||||
transition={{ delay: i * 0.2 }}
|
className="absolute inset-0 flex items-center gap-4"
|
||||||
className="flex gap-4"
|
|
||||||
>
|
>
|
||||||
<span className="text-zinc-600">[{log.time}]</span>
|
<span className="text-zinc-600">[{log.time}]</span>
|
||||||
<span className="text-zinc-400 font-bold w-8">{log.type}</span>
|
<span className={`font-bold w-10 ${log.type === 'LIQ' ? 'text-red-500 bg-red-500/10 px-1 rounded' :
|
||||||
<span className={log.color}>{log.msg}</span>
|
log.type === 'ARB' ? 'text-nofx-gold bg-nofx-gold/10 px-1 rounded' :
|
||||||
|
log.type === 'EXE' ? 'text-green-500' : 'text-zinc-500'
|
||||||
|
}`}>{log.type}</span>
|
||||||
|
<span className={`${log.color}`}>{log.msg}</span>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile View: Vertical Stack */}
|
||||||
|
<div className="md:hidden flex flex-col gap-2 w-full p-4 h-full overflow-hidden">
|
||||||
|
{logs.map((log) => (
|
||||||
|
<div key={log.id} className="flex gap-2 w-full truncate border-b border-zinc-900/50 pb-1 last:border-0">
|
||||||
|
<span className="text-zinc-700 w-16 shrink-0">{log.time.split('.')[0]}</span>
|
||||||
|
<span className={`font-bold w-8 shrink-0 ${log.type === 'LIQ' ? 'text-red-500' :
|
||||||
|
log.type === 'ARB' ? 'text-nofx-gold' :
|
||||||
|
'text-zinc-500'
|
||||||
|
}`}>{log.type}</span>
|
||||||
|
<span className={`${log.color} truncate`}>{log.msg}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,224 +1,324 @@
|
|||||||
import { motion } from 'framer-motion'
|
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 { useState, useEffect } from 'react'
|
||||||
import { OFFICIAL_LINKS } from '../../../constants/branding'
|
import { httpClient } from '../../../lib/httpClient'
|
||||||
|
import { useGitHubStats } from '../../../hooks/useGitHubStats'
|
||||||
|
|
||||||
export default function TerminalHero() {
|
export default function TerminalHero() {
|
||||||
const [text, setText] = useState('')
|
|
||||||
const [githubData, setGithubData] = useState({ stars: '9.4k', forks: '2.4k', subscribers: '74' })
|
// Real-time price state
|
||||||
const fullText = "INITIALIZING NOFX KERNEL... CRYPTO | STOCKS | FOREX | METALS... SYSTEM READY."
|
const [prices, setPrices] = useState<Record<string, string>>({
|
||||||
|
BTC: '...',
|
||||||
|
ETH: '...',
|
||||||
|
SOL: '...',
|
||||||
|
BNB: '...',
|
||||||
|
XRP: '...',
|
||||||
|
DOGE: '...',
|
||||||
|
ADA: '...',
|
||||||
|
AVAX: '...'
|
||||||
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Typing effect
|
const fetchPrices = async () => {
|
||||||
let i = 0
|
const symbols = ['BTC', 'ETH', 'SOL', 'BNB', 'XRP', 'DOGE', 'ADA', 'AVAX']
|
||||||
const timer = setInterval(() => {
|
|
||||||
setText(fullText.slice(0, i))
|
|
||||||
i++
|
|
||||||
if (i > fullText.length) clearInterval(timer)
|
|
||||||
}, 30)
|
|
||||||
|
|
||||||
// Fetch GitHub Data
|
// We use Promise.all to fetch them in parallel for now, or sequentially if rate limited.
|
||||||
fetch('https://api.github.com/repos/NoFxAiOS/nofx')
|
// Parallel is better for UI responsiveness.
|
||||||
.then(res => res.json())
|
try {
|
||||||
.then(data => {
|
const results = await Promise.all(symbols.map(async (sym) => {
|
||||||
if (data.stargazers_count) {
|
try {
|
||||||
setGithubData({
|
const res = await httpClient.get(`/api/klines?symbol=${sym}USDT&interval=1m&limit=1`)
|
||||||
stars: (data.stargazers_count / 1000).toFixed(1) + 'k',
|
if (res.success && res.data?.length > 0) {
|
||||||
forks: (data.forks_count / 1000).toFixed(1) + 'k',
|
const closePrice = parseFloat(res.data[0].close)
|
||||||
subscribers: data.subscribers_count?.toString() || '74'
|
// 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) {
|
||||||
.catch(err => console.error("Failed to fetch GitHub stats", err))
|
// ignore individual failures
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}))
|
||||||
|
|
||||||
return () => clearInterval(timer)
|
const newPrices: Record<string, string> = {}
|
||||||
|
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 (
|
return (
|
||||||
<section className="relative w-full min-h-screen bg-nofx-bg text-nofx-text overflow-hidden flex flex-col items-center justify-center pt-20">
|
<section className="relative w-full min-h-screen bg-nofx-bg text-nofx-text overflow-hidden flex flex-col pt-20">
|
||||||
|
|
||||||
{/* 1. ARCHITECTURAL BACKGROUND / HOLOGRAPHIC CONSTRUCT */}
|
{/* BACKGROUND LAYERS */}
|
||||||
<div className="absolute inset-0 z-0 overflow-hidden pointer-events-none select-none">
|
{/* 1. Grid */}
|
||||||
|
<div className="absolute inset-0 bg-[url('https://grainy-gradients.vercel.app/noise.svg')] opacity-20 mix-blend-soft-light pointer-events-none"></div>
|
||||||
|
<div className="absolute inset-0 bg-grid-pattern opacity-[0.03] pointer-events-none"></div>
|
||||||
|
|
||||||
{/* The Mascot "Ghost" in the Machine - PREMIUM & CLEAN */}
|
{/* 2. World Map / Data Viz Background (Abstract) */}
|
||||||
<div className="absolute right-0 bottom-0 w-[80vw] lg:w-[45vw] h-[85vh] opacity-90 mix-blend-normal flex items-end justify-end">
|
<div className="absolute inset-0 flex items-center justify-center opacity-10 pointer-events-none">
|
||||||
<div className="relative w-full h-full">
|
<div className="w-[80vw] h-[80vw] rounded-full border border-nofx-gold/20 animate-pulse-slow"></div>
|
||||||
<img
|
<div className="absolute w-[60vw] h-[60vw] rounded-full border border-dashed border-nofx-accent/20 animate-[spin_60s_linear_infinite]"></div>
|
||||||
src="/images/nofx_mascot.png"
|
</div>
|
||||||
alt=""
|
|
||||||
className="w-full h-full object-contain object-bottom drop-shadow-[0_0_50px_rgba(240,185,11,0.2)]"
|
|
||||||
style={{
|
|
||||||
maskImage: 'linear-gradient(to top, black 60%, transparent 100%)',
|
|
||||||
filter: 'grayscale(100%) contrast(110%) brightness(110%) sepia(20%) hue-rotate(320deg)'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Clean Horizontal Scanline Overlay */}
|
{/* 3. Gradient Spots */}
|
||||||
<div className="absolute inset-0 bg-[url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0IiBoZWlnaHQ9IjQiPgo8cmVjdCB3aWR0aD0iNCIgaGVpZ2h0PSIxIiBmaWxsPSJyZ2JhKDAsIDAsIDAsIDAuMykiIC8+Cjwvc3ZnPg==')] opacity-50 mix-blend-overlay pointer-events-none" />
|
<div className="absolute top-[-10%] left-[-10%] w-[40vw] h-[40vw] bg-nofx-gold/10 rounded-full blur-[120px] pointer-events-none"></div>
|
||||||
|
<div className="absolute bottom-[-10%] right-[-10%] w-[40vw] h-[40vw] bg-nofx-accent/5 rounded-full blur-[120px] pointer-events-none"></div>
|
||||||
|
|
||||||
{/* Subtle Glow Behind */}
|
{/* CONTENT GRID */}
|
||||||
<div className="absolute right-10 bottom-10 w-64 h-64 bg-nofx-gold/20 rounded-full blur-[100px] -z-10" />
|
<div className="relative z-10 flex-1 grid grid-cols-1 lg:grid-cols-12 gap-0 lg:gap-8 max-w-[1800px] mx-auto w-full px-6 h-full pb-20 pt-10 pointer-events-none">
|
||||||
|
|
||||||
|
{/* LEFT COLUMN: TELEMETRY & STATUS */}
|
||||||
|
<div className="hidden lg:flex col-span-3 flex-col justify-between h-full border-r border-white/5 pr-8 py-10 pointer-events-auto">
|
||||||
|
|
||||||
|
{/* Top: System Health */}
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="tech-border p-4 bg-black/40 backdrop-blur-sm">
|
||||||
|
<h3 className="text-xs font-mono text-nofx-gold mb-4 flex items-center gap-2">
|
||||||
|
<Activity className="w-3 h-3" /> SYSTEM_DIAGNOSTICS
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-3 font-mono text-[10px] text-zinc-400">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span>KERNEL_LATENCY</span>
|
||||||
|
<span className="text-nofx-accent">12ms</span>
|
||||||
|
</div>
|
||||||
|
<div className="w-full h-1 bg-zinc-800 rounded-full overflow-hidden">
|
||||||
|
<div className="w-[90%] h-full bg-nofx-accent/50"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span>MEMORY_INTEGRITY</span>
|
||||||
|
<span className="text-nofx-success">100%</span>
|
||||||
|
</div>
|
||||||
|
<div className="w-full h-1 bg-zinc-800 rounded-full overflow-hidden">
|
||||||
|
<div className="w-full h-full bg-nofx-success/50"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span>UPTIME</span>
|
||||||
|
<span className="text-white">99.999%</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Clean Geometric Grid */}
|
<div className="p-4 border border-zinc-800/50 rounded bg-zinc-900/20">
|
||||||
<svg className="absolute inset-0 w-full h-full opacity-10" xmlns="http://www.w3.org/2000/svg">
|
<div className="flex items-center gap-3 text-zinc-500 mb-2">
|
||||||
<defs>
|
<Shield className="w-4 h-4" />
|
||||||
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
|
<span className="text-[10px] font-mono tracking-widest">SECURITY PROTOCOLS</span>
|
||||||
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="currentColor" strokeWidth="0.5" className="text-zinc-500" />
|
|
||||||
</pattern>
|
|
||||||
</defs>
|
|
||||||
<rect width="100%" height="100%" fill="url(#grid)" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="relative z-10 flex flex-col items-center text-center max-w-[1400px] px-6 w-full h-full justify-center">
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 w-full items-center">
|
|
||||||
|
|
||||||
{/* LEFT COLUMN: Main System Interface */}
|
|
||||||
<div className="col-span-1 lg:col-span-8 text-left z-30 flex flex-col justify-center h-full">
|
|
||||||
|
|
||||||
{/* System Status Tag */}
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: -10 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
className="flex flex-wrap items-center gap-3 mb-8"
|
|
||||||
>
|
|
||||||
<div className="px-3 py-1 border border-nofx-gold/30 bg-nofx-gold/5 rounded-sm text-nofx-gold text-xs font-mono flex items-center gap-2 shadow-[0_0_15px_rgba(240,185,11,0.2)]">
|
|
||||||
<div className="w-1.5 h-1.5 bg-nofx-gold rounded-full animate-pulse" />
|
|
||||||
SYSTEM ONLINE
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
{/* Main Headline with Project Specifics */}
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, x: -20 }}
|
|
||||||
animate={{ opacity: 1, x: 0 }}
|
|
||||||
transition={{ delay: 0.2 }}
|
|
||||||
className="relative"
|
|
||||||
>
|
|
||||||
<h1 className="text-6xl md:text-8xl xl:text-9xl font-black tracking-tighter leading-[0.85] mb-8 text-white">
|
|
||||||
AGENTIC <br />
|
|
||||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-nofx-gold via-white to-nofx-gold animate-shimmer bg-[length:200%_100%]">TRADING OS</span>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
{/* SVG Connector Line */}
|
|
||||||
<div className="absolute -left-10 top-2 bottom-2 w-px bg-zinc-800 hidden lg:block">
|
|
||||||
<div className="absolute top-0 left-[-1px] w-[3px] h-8 bg-nofx-gold" />
|
|
||||||
<div className="absolute bottom-0 left-[-1px] w-[3px] h-8 bg-zinc-600" />
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
{/* Typing Terminal Output */}
|
|
||||||
<div className="h-24 mb-10 font-mono text-zinc-400 text-sm flex flex-col justify-start gap-3 max-w-2xl border-l-2 border-zinc-800 pl-6">
|
|
||||||
<div className="flex items-center gap-2 text-nofx-gold">
|
|
||||||
<span>></span> {text}<span className="animate-pulse bg-nofx-gold w-2 h-4 block"></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Clean Markets Row */}
|
|
||||||
<div className="flex gap-6 text-[10px] md:text-xs text-zinc-500 font-bold tracking-widest uppercase">
|
|
||||||
<span className="flex items-center gap-2 hover:text-white transition-colors"><Network className="w-3 h-3" /> CRYPTO</span>
|
|
||||||
<span className="flex items-center gap-2 hover:text-white transition-colors"><Activity className="w-3 h-3" /> STOCKS</span>
|
|
||||||
<span className="flex items-center gap-2 hover:text-white transition-colors"><Layers className="w-3 h-3" /> FOREX</span>
|
|
||||||
<span className="flex items-center gap-2 hover:text-white transition-colors"><Cpu className="w-3 h-3" /> METALS</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Primary Actions */}
|
|
||||||
<div className="flex flex-col sm:flex-row gap-4 w-full max-w-lg">
|
|
||||||
<button
|
|
||||||
onClick={() => document.getElementById('market-scanner')?.scrollIntoView({ behavior: 'smooth' })}
|
|
||||||
className="group relative px-8 py-4 bg-nofx-gold text-black font-bold font-mono hover:bg-white transition-all flex items-center justify-between min-w-[200px] hover:shadow-[0_0_20px_rgba(240,185,11,0.4)]"
|
|
||||||
style={{ clipPath: 'polygon(0 0, 100% 0, 100% 80%, 90% 100%, 0% 100%)' }}
|
|
||||||
>
|
|
||||||
<span>DEPLOY TRADERS</span>
|
|
||||||
<ArrowRight className="w-5 h-5 group-hover:translate-x-1 transition-transform" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<a
|
|
||||||
href={OFFICIAL_LINKS.github}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
className="px-8 py-4 border border-zinc-700 bg-black/50 backdrop-blur-sm text-zinc-300 font-mono hover:border-nofx-accent hover:text-nofx-accent transition-all flex items-center justify-between min-w-[200px]"
|
|
||||||
style={{ clipPath: 'polygon(0 0, 100% 0, 100% 100%, 10% 100%, 0% 80%)' }}
|
|
||||||
>
|
|
||||||
<span>SOURCE CODE</span>
|
|
||||||
<TerminalIcon className="w-4 h-4" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* RIGHT COLUMN: Modules & Data HUD */}
|
|
||||||
<div className="col-span-1 lg:col-span-4 flex flex-col gap-6 mt-12 lg:mt-0 z-20">
|
|
||||||
|
|
||||||
{/* Module 1: GitHub Intelligence */}
|
|
||||||
<div className="border border-zinc-800 bg-black/80 backdrop-blur-md p-6 relative group overflow-hidden">
|
|
||||||
<div className="absolute top-0 right-0 w-20 h-20 bg-nofx-gold/5 rounded-bl-full -mr-10 -mt-10 transition-transform group-hover:scale-150" />
|
|
||||||
|
|
||||||
<div className="flex justify-between items-start mb-4">
|
|
||||||
<div className="text-xs font-mono text-zinc-500 flex items-center gap-2">
|
|
||||||
<Users className="w-4 h-4 text-nofx-gold" /> COMMUNITY UPLINK
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
<div className="w-1.5 h-1.5 bg-green-500 rounded-full animate-pulse" />
|
<div className="h-1 flex-1 bg-nofx-gold"></div>
|
||||||
|
<div className="h-1 flex-1 bg-nofx-gold"></div>
|
||||||
|
<div className="h-1 flex-1 bg-nofx-gold"></div>
|
||||||
|
<div className="h-1 flex-1 bg-zinc-800"></div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 text-right text-[10px] text-nofx-gold/80 font-mono">LEVEL 3 ACTIVATE</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
{/* Bottom: Network Log */}
|
||||||
<div>
|
<div className="font-mono text-[10px] text-zinc-600 space-y-1 opacity-70">
|
||||||
<div className="text-2xl font-bold text-white flex items-center gap-1">
|
<div>> CONNECTING TO MAINNET... OK</div>
|
||||||
{githubData.stars} <Star className="w-3 h-3 text-nofx-gold fill-nofx-gold" />
|
<div>> SYNCING NODES (424/424)... OK</div>
|
||||||
</div>
|
<div>> LOADING ASSETS... DONE</div>
|
||||||
<div className="text-[10px] text-zinc-500 uppercase tracking-wider">Active Star-gazers</div>
|
<div className="animate-pulse">> AWAITING USER INPUT_</div>
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="text-2xl font-bold text-white flex items-center gap-1">
|
|
||||||
{githubData.forks} <GitFork className="w-3 h-3 text-zinc-500" />
|
|
||||||
</div>
|
|
||||||
<div className="text-[10px] text-zinc-500 uppercase tracking-wider">Protocol Forks</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Module 2: System Capabilities (Specific to NoFX) */}
|
|
||||||
<div className="border border-zinc-800 bg-black/60 backdrop-blur-sm p-6 space-y-3 hidden md:block">
|
|
||||||
<div className="text-xs font-mono text-zinc-500 mb-2">ACTIVE MODULES</div>
|
|
||||||
|
|
||||||
<div className="flex justify-between items-center text-sm font-mono border-b border-zinc-900 pb-2">
|
|
||||||
<span className="text-zinc-300">STRATEGY STUDIO</span>
|
|
||||||
<span className="text-green-500 text-xs">READY</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between items-center text-sm font-mono border-b border-zinc-900 pb-2">
|
|
||||||
<span className="text-zinc-300">DEBATE ARENA</span>
|
|
||||||
<span className="text-green-500 text-xs text-nofx-accent animate-pulse">Running</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between items-center text-sm font-mono pb-2">
|
|
||||||
<span className="text-zinc-300">BACKTEST LAB</span>
|
|
||||||
<span className="text-zinc-500 text-xs">Idle</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
{/* CENTER COLUMN: MAIN ACTION */}
|
||||||
|
<div className="col-span-1 lg:col-span-6 flex flex-col items-center justify-center text-center relative z-20 pointer-events-auto">
|
||||||
|
|
||||||
</div>
|
{/* Project Identity Chip */}
|
||||||
|
<motion.div
|
||||||
</div>
|
initial={{ opacity: 0, y: -20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
{/* Decorative Footer */}
|
className="mb-8 inline-flex items-center gap-3 px-4 py-2 rounded-full border border-nofx-gold/20 bg-nofx-gold/5 backdrop-blur-md"
|
||||||
<div className="absolute bottom-0 w-full border-t border-zinc-800 bg-black/90 backdrop-blur-md p-3 flex flex-wrap justify-between items-center text-[10px] md:text-xs text-zinc-500 font-mono z-20">
|
>
|
||||||
<div className="flex gap-6 px-4">
|
<span className="relative flex h-2 w-2">
|
||||||
<span className="flex items-center gap-2">
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-nofx-gold opacity-75"></span>
|
||||||
<div className="w-2 h-2 bg-nofx-gold/50 rounded-full" />
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-nofx-gold"></span>
|
||||||
NOFX-OS
|
|
||||||
</span>
|
</span>
|
||||||
<span className="hidden sm:inline">24H VOL: $42.8M</span>
|
<span className="text-xs font-mono text-nofx-gold tracking-widest">NOFX OPEN-SOURCE AGENTIC OS</span>
|
||||||
<span className="hidden sm:inline">ACTIVE AGENTS: 1,024</span>
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Main Title - Massive & Impactful */}
|
||||||
|
<h1 className="text-6xl md:text-8xl lg:text-9xl font-black tracking-tighter leading-[0.8] mb-6 select-none bg-clip-text text-transparent bg-gradient-to-b from-white via-white to-zinc-600">
|
||||||
|
AGENTIC<br />
|
||||||
|
<span className="text-stroke-1 text-transparent bg-clip-text bg-gradient-to-r from-nofx-gold via-white to-nofx-gold animate-shimmer bg-[length:200%_auto]">TRADING</span>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className="max-w-xl text-zinc-400 text-lg mb-10 font-light leading-relaxed">
|
||||||
|
The World's First Open-Source Agentic Trading OS.
|
||||||
|
Deploy autonomous high-frequency trading agents powered by advanced LLMs.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Command Line Input Simulation */}
|
||||||
|
<div className="w-full max-w-lg h-12 bg-black/50 border border-zinc-800 rounded flex items-center px-4 mb-10 font-mono text-sm shadow-2xl backdrop-blur-sm group hover:border-nofx-gold/50 transition-colors cursor-text" onClick={() => document.getElementById('market-scanner')?.scrollIntoView({ behavior: 'smooth' })}>
|
||||||
|
<span className="text-nofx-success mr-2">➜</span>
|
||||||
|
<span className="text-nofx-accent mr-2">~</span>
|
||||||
|
<span className="text-zinc-500">deploy agent --strategy=hft</span>
|
||||||
|
<span className="w-2 h-4 bg-nofx-gold ml-1 animate-pulse"></span>
|
||||||
</div>
|
</div>
|
||||||
<div className="px-4 flex gap-4">
|
|
||||||
<span className="text-nofx-gold">ENCRYPTED CONNECTION</span>
|
{/* CTA Buttons */}
|
||||||
|
<div className="flex flex-col sm:flex-row gap-4 w-full justify-center">
|
||||||
|
<button
|
||||||
|
onClick={() => document.getElementById('market-scanner')?.scrollIntoView({ behavior: 'smooth' })}
|
||||||
|
className="group relative overflow-hidden bg-nofx-gold text-black px-8 py-4 font-bold font-mono tracking-wider hover:scale-105 transition-transform duration-200"
|
||||||
|
style={{ clipPath: 'polygon(10% 0, 100% 0, 100% 70%, 90% 100%, 0 100%, 0 30%)' }}
|
||||||
|
>
|
||||||
|
<span className="relative z-10 flex items-center gap-2">
|
||||||
|
INITIALIZE PROTOCOL <ArrowRight className="w-4 h-4" />
|
||||||
|
</span>
|
||||||
|
<div className="absolute inset-0 bg-white/20 translate-y-full group-hover:translate-y-0 transition-transform duration-300"></div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Community Stats Row */}
|
||||||
|
<CommunityStats />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* RIGHT COLUMN: HOLOGRAPHIC DISPLAY - Absolute Overlay for "Far Right" Effect */}
|
||||||
|
<div className="absolute top-0 right-0 h-full w-[45vw] hidden lg:flex pointer-events-none items-center justify-center z-0">
|
||||||
|
<div className="relative w-full h-full flex items-center justify-center perspective-1000">
|
||||||
|
{/* 3D Hologram Effect Container */}
|
||||||
|
<div className="relative w-full h-[90%] flex items-center justify-center transform-style-3d rotate-y-[-12deg]">
|
||||||
|
|
||||||
|
{/* Scanning Grid behind Mascot */}
|
||||||
|
<div className="absolute inset-x-0 top-[10%] bottom-[10%] bg-[linear-gradient(rgba(0,240,255,0.05)_1px,transparent_1px),linear-gradient(90deg,rgba(0,240,255,0.05)_1px,transparent_1px)] bg-[size:20px_20px] [mask-image:radial-gradient(ellipse_at_center,black_40%,transparent_80%)]"></div>
|
||||||
|
|
||||||
|
{/* The Mascot Image with Glitch/Holo Effects */}
|
||||||
|
<div className="relative z-10 w-full h-full opacity-90 transition-all duration-500 hover:opacity-100 group flex flex-col justify-end pointer-events-auto">
|
||||||
|
<div className="absolute inset-0 bg-nofx-accent/10 blur-[80px] rounded-full animate-pulse-slow transition-colors duration-500 group-hover:bg-nofx-gold/40"></div>
|
||||||
|
<img
|
||||||
|
src="/images/nofx_mascot.png"
|
||||||
|
alt="Agent NoFX"
|
||||||
|
className="w-full h-full object-contain object-bottom filter drop-shadow-[0_0_25px_rgba(0,240,255,0.2)] contrast-110 saturate-0 group-hover:saturate-100 group-hover:drop-shadow-[0_0_35px_rgba(234,179,8,0.5)] transition-all duration-500"
|
||||||
|
style={{ maskImage: 'linear-gradient(to bottom, black 90%, transparent 100%)' }}
|
||||||
|
/>
|
||||||
|
{/* Holo Scan Line */}
|
||||||
|
<div className="absolute w-full h-2 bg-nofx-accent/30 drop-shadow-[0_0_10px_rgba(0,240,255,0.8)] top-0 animate-scan-fast pointer-events-none"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Floating Data Widgets around Hologram */}
|
||||||
|
<motion.div
|
||||||
|
animate={{ y: [0, -10, 0] }}
|
||||||
|
transition={{ duration: 4, repeat: Infinity, ease: "easeInOut" }}
|
||||||
|
className="absolute top-[30%] left-[10%] bg-black/80 border border-nofx-accent/30 p-2 rounded backdrop-blur-md shadow-neon-blue"
|
||||||
|
>
|
||||||
|
<Cpu className="w-5 h-5 text-nofx-accent" />
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
animate={{ y: [0, 10, 0] }}
|
||||||
|
transition={{ duration: 5, repeat: Infinity, ease: "easeInOut", delay: 1 }}
|
||||||
|
className="absolute bottom-[20%] right-[20%] bg-black/80 border border-nofx-gold/30 p-2 rounded backdrop-blur-md shadow-neon"
|
||||||
|
>
|
||||||
|
<Lock className="w-5 h-5 text-nofx-gold" />
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* FLOATING TICKER FOOTER */}
|
||||||
|
<div className="absolute bottom-0 w-full bg-black/80 border-t border-zinc-800/50 backdrop-blur-md z-30 overflow-hidden py-2 flex items-center">
|
||||||
|
<div className="flex animate-marquee whitespace-nowrap gap-12 text-xs font-mono text-zinc-500 px-4">
|
||||||
|
<span className="flex items-center gap-2"><Globe className="w-3 h-3 text-zinc-600" /> GLOBAL MARKET ACCESS</span>
|
||||||
|
<span className="flex items-center gap-2 text-nofx-gold"><Zap className="w-3 h-3" /> FLASH LOANS ENABLED</span>
|
||||||
|
<span className="flex items-center gap-2"><Wifi className="w-3 h-3 text-green-500" /> LOW LATENCY LINK: 12ms</span>
|
||||||
|
|
||||||
|
{/* Dynamic Coins */}
|
||||||
|
{Object.entries(prices).map(([symbol, price]) => (
|
||||||
|
<span key={symbol} className="flex items-center gap-2">
|
||||||
|
{symbol.toUpperCase()}/USDT <span className="text-nofx-success">${price}</span>
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<span className="flex items-center gap-2"><CircuitBoard className="w-3 h-3 text-nofx-accent" /> AI MODEL: GEMINI-PRO-1.5</span>
|
||||||
|
|
||||||
|
{/* Duplicate sequence for seamless loop effect (basic set) */}
|
||||||
|
{Object.entries(prices).map(([symbol, price]) => (
|
||||||
|
<span key={`${symbol} -dup`} className="flex items-center gap-2 md:hidden">
|
||||||
|
{symbol.toUpperCase()}/USDT <span className="text-nofx-success">${price}</span>
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* CRT OVERLAY (Global) */}
|
||||||
|
<div className="absolute inset-0 crt-overlay pointer-events-none z-50 opacity-40"></div>
|
||||||
</section >
|
</section >
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className="mt-12 grid grid-cols-2 md:grid-cols-4 gap-4 w-full max-w-4xl">
|
||||||
|
{stats.map((stat, i) => (
|
||||||
|
<a
|
||||||
|
key={i}
|
||||||
|
href={stat.href}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="flex flex-col items-center justify-center p-3 rounded bg-black/40 border border-zinc-800/50 backdrop-blur-sm group hover:border-nofx-gold/30 transition-all cursor-pointer hover:bg-white/5"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 mb-1">
|
||||||
|
<stat.icon className={`w-4 h-4 ${stat.color}`} />
|
||||||
|
<span className="text-[10px] font-mono text-zinc-500 tracking-wider">{stat.label}</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-xl font-bold font-mono text-white group-hover:text-nofx-gold transition-colors">{stat.value}</span>
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useState, useEffect } from 'react'
|
|||||||
interface GitHubStats {
|
interface GitHubStats {
|
||||||
stars: number
|
stars: number
|
||||||
forks: number
|
forks: number
|
||||||
|
contributors: number
|
||||||
createdAt: string
|
createdAt: string
|
||||||
daysOld: number
|
daysOld: number
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
@@ -13,6 +14,7 @@ export function useGitHubStats(owner: string, repo: string): GitHubStats {
|
|||||||
const [stats, setStats] = useState<GitHubStats>({
|
const [stats, setStats] = useState<GitHubStats>({
|
||||||
stars: 0,
|
stars: 0,
|
||||||
forks: 0,
|
forks: 0,
|
||||||
|
contributors: 0,
|
||||||
createdAt: '',
|
createdAt: '',
|
||||||
daysOld: 0,
|
daysOld: 0,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
@@ -22,26 +24,52 @@ export function useGitHubStats(owner: string, repo: string): GitHubStats {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchGitHubStats = async () => {
|
const fetchGitHubStats = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
// Fetch basic repo info
|
||||||
`https://api.github.com/repos/${owner}/${repo}`
|
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) {
|
// Fetch contributors count (using Link header trick for large numbers, or length for small)
|
||||||
throw new Error('Failed to fetch GitHub stats')
|
// 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
|
// Calculate days since creation
|
||||||
const createdDate = new Date(data.created_at)
|
const createdDate = new Date(repoData.created_at)
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const diffTime = Math.abs(now.getTime() - createdDate.getTime())
|
const diffTime = Math.abs(now.getTime() - createdDate.getTime())
|
||||||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
||||||
|
|
||||||
setStats({
|
setStats({
|
||||||
stars: data.stargazers_count,
|
stars: repoData.stargazers_count,
|
||||||
forks: data.forks_count,
|
forks: repoData.forks_count,
|
||||||
createdAt: data.created_at,
|
contributors: contributorsCount > 0 ? contributorsCount : 0, // Fallback
|
||||||
|
createdAt: repoData.created_at,
|
||||||
daysOld: diffDays,
|
daysOld: diffDays,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: null,
|
error: null,
|
||||||
|
|||||||
@@ -13,21 +13,27 @@ html {
|
|||||||
:root {
|
:root {
|
||||||
/* NoFX Neo-Gold Design System */
|
/* NoFX Neo-Gold Design System */
|
||||||
--nofx-gold: #F0B90B;
|
--nofx-gold: #F0B90B;
|
||||||
--nofx-bg: #0B0E11;
|
--nofx-bg: #05070A;
|
||||||
--nofx-accent: #00F0FF;
|
--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);
|
--nofx-border: rgba(240, 185, 11, 0.2);
|
||||||
|
|
||||||
--background: #0B0E11;
|
/* Cinematic Glows */
|
||||||
--header-bg: rgba(11, 14, 17, 0.9);
|
--glow-primary: 0 0 20px rgba(240, 185, 11, 0.3);
|
||||||
/* Glass header */
|
--glow-accent: 0 0 20px rgba(0, 240, 255, 0.3);
|
||||||
--glass-bg: rgba(11, 14, 17, 0.6);
|
--glow-text: 0 0 10px rgba(240, 185, 11, 0.5);
|
||||||
--glass-border: rgba(240, 185, 11, 0.1);
|
|
||||||
|
|
||||||
--panel-bg: #15181D;
|
--background: #05070A;
|
||||||
--panel-bg-hover: #1E2329;
|
--header-bg: rgba(2, 3, 4, 0.85);
|
||||||
--panel-border: rgba(255, 255, 255, 0.08);
|
/* Deep Abyssal */
|
||||||
--panel-border-hover: rgba(240, 185, 11, 0.4);
|
|
||||||
|
--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;
|
--foreground: #EAECEF;
|
||||||
--text-primary: #EAECEF;
|
--text-primary: #EAECEF;
|
||||||
@@ -42,19 +48,15 @@ html {
|
|||||||
--binance-red: #F6465D;
|
--binance-red: #F6465D;
|
||||||
--binance-red-bg: rgba(246, 70, 93, 0.12);
|
--binance-red-bg: rgba(246, 70, 93, 0.12);
|
||||||
--binance-red-border: rgba(246, 70, 93, 0.3);
|
--binance-red-border: rgba(246, 70, 93, 0.3);
|
||||||
|
--binance-yellow: #F0B90B;
|
||||||
|
|
||||||
/* Shadows */
|
/* Shadows */
|
||||||
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.2);
|
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||||
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.3);
|
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
|
||||||
--shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.4);
|
--shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.6);
|
||||||
--shadow-glow: 0 0 20px rgba(240, 185, 11, 0.2);
|
--shadow-glow: 0 0 30px rgba(240, 185, 11, 0.15);
|
||||||
|
|
||||||
font-family:
|
font-family: 'IBM Plex Mono', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
'Inter',
|
|
||||||
-apple-system,
|
|
||||||
BlinkMacSystemFont,
|
|
||||||
'Segoe UI',
|
|
||||||
sans-serif;
|
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
@@ -64,8 +66,69 @@ html {
|
|||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-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 {
|
body {
|
||||||
@@ -73,7 +136,7 @@ body {
|
|||||||
min-width: 320px;
|
min-width: 320px;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background-color: var(--background);
|
background-color: var(--background);
|
||||||
background-image: none;
|
background-image: radial-gradient(circle at 50% 0%, #151921 0%, #05070a 60%);
|
||||||
background-attachment: fixed;
|
background-attachment: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,68 @@ export default {
|
|||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
'nofx-gold': '#F0B90B',
|
'nofx-gold': {
|
||||||
'nofx-gold-dim': 'rgba(240, 185, 11, 0.15)',
|
DEFAULT: '#F0B90B',
|
||||||
'nofx-bg': '#0B0E11',
|
dim: 'rgba(240, 185, 11, 0.1)',
|
||||||
'nofx-accent': '#00F0FF',
|
glow: 'rgba(240, 185, 11, 0.5)',
|
||||||
'nofx-text': '#EAECEF',
|
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)',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user