| <!DOCTYPE html> |
| <html lang="vi" class="scroll-smooth"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> |
| <title>Crypto Intel Fortress — Offline-First & Bulletproof</title> |
| <meta name="description" content="Real-time Crypto Security & On-chain Monitor."> |
| |
| <meta name="referrer" content="no-referrer-when-downgrade"> |
| |
| |
| <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🛡️</text></svg>"> |
|
|
| |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script> |
| tailwind.config = { |
| darkMode: 'class', |
| theme: { |
| extend: { |
| fontFamily: { |
| sans: ['Inter', 'system-ui', 'sans-serif'], |
| mono: ['JetBrains Mono', 'monospace'], |
| }, |
| animation: { |
| 'fade-in': 'fadeIn 0.6s cubic-bezier(0.16, 1, 0.3, 1)', |
| 'pulse-slow': 'pulse 4s cubic-bezier(0.4, 0, 0.6, 1) infinite', |
| }, |
| keyframes: { |
| fadeIn: { '0%': { opacity: '0' }, '100%': { opacity: '1' } } |
| }, |
| colors: { |
| 'fortress-bg': '#020617', |
| } |
| } |
| } |
| } |
| </script> |
|
|
| |
| <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script> |
| <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> |
| <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> |
|
|
| <style> |
| body { background-color: #000000; color: #e2e8f0; } |
| ::-webkit-scrollbar { width: 6px; height: 6px; } |
| ::-webkit-scrollbar-track { background: #020617; } |
| ::-webkit-scrollbar-thumb { background: #334155; border-radius: 3px; } |
| ::-webkit-scrollbar-thumb:hover { background: #475569; } |
| .glass-panel { |
| background: rgba(15, 23, 42, 0.6); |
| backdrop-filter: blur(16px); |
| -webkit-backdrop-filter: blur(16px); |
| border: 1px solid rgba(255, 255, 255, 0.05); |
| box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1); |
| } |
| .no-scrollbar::-webkit-scrollbar { display: none; } |
| .no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; } |
| |
| |
| .twitter-container iframe { |
| width: 100% !important; |
| height: 100% !important; |
| border-radius: 8px; |
| } |
| </style> |
| </head> |
| <body class="min-h-screen bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-slate-900 via-black to-black selection:bg-cyan-500/30 selection:text-cyan-200"> |
| <div id="root"></div> |
|
|
| <script type="text/babel"> |
| const { useState, useEffect, useRef, useMemo } = React; |
| |
| |
| const Icon = ({ children, size = 20, className = "" }) => ( |
| <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}> |
| {children} |
| </svg> |
| ); |
| |
| const icons = { |
| shield: <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10"/>, |
| search: <><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></>, |
| trash: <><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></>, |
| wifi: <path d="M5 12.55a11 11 0 0 1 14.08 0M1.42 9a16 16 0 0 1 21.16 0M8.53 16.11a6 6 0 0 1 6.95 0M12 20h.01"/>, |
| wifiOff: <><line x1="1" y1="1" x2="23" y2="23"/><path d="M16.72 11.06A10.94 10.94 0 0 1 19 12.55"/><path d="M5 12.55a11 11 0 0 1 7.67-9.77"/><path d="M8.53 16.11a6 6 0 0 1 6.95 0"/><path d="M12 20h.01"/></>, |
| external: <><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></>, |
| plus: <><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></>, |
| alert: <><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></> |
| }; |
| |
| |
| const DEFAULT_ACCOUNTS = [ |
| { handle: "PeckShieldAlert", name: "PeckShield", cat: "Security" }, |
| { handle: "realScamSniffer", name: "Scam Sniffer", cat: "Security" }, |
| { handle: "ZachXBT", name: "ZachXBT", cat: "Security" }, |
| { handle: "CertiKAlert", name: "CertiK", cat: "Security" }, |
| { handle: "lookonchain", name: "Lookonchain", cat: "On-chain" }, |
| { handle: "whale_alert", name: "Whale Alert", cat: "On-chain" }, |
| { handle: "ArkhamIntel", name: "Arkham", cat: "On-chain" }, |
| { handle: "WuBlockchain", name: "Wu Blockchain", cat: "News" }, |
| { handle: "Tier10k", name: "DB (Tier10k)", cat: "News" }, |
| { handle: "DeFiLlama", name: "DeFiLlama", cat: "Data" } |
| ]; |
| |
| const CATEGORIES = ["All", "Security", "On-chain", "News", "Data", "Custom"]; |
| const STORAGE_KEY = 'CRYPTO_FORTRESS_V70'; |
| |
| |
| |
| const Skeleton = () => ( |
| <div className="w-full h-full p-4 flex flex-col gap-4 animate-pulse-slow"> |
| <div className="flex gap-3"> |
| <div className="w-10 h-10 rounded-full bg-slate-800/50"></div> |
| <div className="flex-1 space-y-2"> |
| <div className="h-3 bg-slate-800/50 rounded w-1/3"></div> |
| <div className="h-2 bg-slate-800/30 rounded w-1/4"></div> |
| </div> |
| </div> |
| <div className="flex-1 bg-slate-800/20 rounded-lg border border-slate-800/30"></div> |
| </div> |
| ); |
| |
| |
| const TwitterCard = React.memo(({ account, onRemove, isOnline }) => { |
| const containerRef = useRef(null); |
| const [status, setStatus] = useState('loading'); |
| |
| useEffect(() => { |
| if (!isOnline) { |
| setStatus('offline'); |
| return; |
| } |
| |
| setStatus('loading'); |
| |
| let isMounted = true; |
| const container = containerRef.current; |
| |
| const initWidget = () => { |
| if (!window.twttr || !window.twttr.widgets) { |
| setTimeout(initWidget, 500); |
| return; |
| } |
| |
| if (!container) return; |
| container.innerHTML = ''; |
| |
| |
| window.twttr.widgets.createTimeline( |
| { |
| sourceType: 'profile', |
| screenName: account.handle |
| }, |
| container, |
| { |
| theme: 'dark', |
| height: 400, |
| chrome: 'noheader,nofooter,noborders,transparent,noscrollbar', |
| dnt: true |
| } |
| ).then((el) => { |
| if (isMounted) { |
| if (el) { |
| setStatus('loaded'); |
| } else { |
| |
| setStatus('error'); |
| } |
| } |
| }).catch(() => { |
| if (isMounted) setStatus('error'); |
| }); |
| }; |
| |
| |
| const observer = new IntersectionObserver((entries) => { |
| if (entries[0].isIntersecting) { |
| initWidget(); |
| observer.disconnect(); |
| } |
| }, { rootMargin: '100px' }); |
| |
| if (container) observer.observe(container); |
| |
| |
| const timer = setTimeout(() => { |
| if (isMounted && status === 'loading') { |
| setStatus('error'); |
| } |
| }, 4000); |
| |
| return () => { |
| isMounted = false; |
| observer.disconnect(); |
| clearTimeout(timer); |
| }; |
| }, [account.handle, isOnline]); |
| |
| const colorMap = { |
| Security: "text-red-400 border-red-500/30 bg-red-500/10", |
| "On-chain": "text-cyan-400 border-cyan-500/30 bg-cyan-500/10", |
| News: "text-emerald-400 border-emerald-500/30 bg-emerald-500/10", |
| Data: "text-purple-400 border-purple-500/30 bg-purple-500/10", |
| Custom: "text-amber-400 border-amber-500/30 bg-amber-500/10" |
| }; |
| |
| return ( |
| <div className="glass-panel rounded-2xl overflow-hidden flex flex-col h-[450px] group transition-all duration-300 hover:border-slate-600 hover:shadow-2xl hover:shadow-cyan-900/10 animate-fade-in relative"> |
| |
| {/* Card Header */} |
| <div className="p-4 border-b border-white/5 bg-slate-900/50 flex justify-between items-center z-10 shrink-0"> |
| <div className="flex items-center gap-3 overflow-hidden"> |
| <div className={`text-[10px] font-mono font-bold px-2 py-0.5 rounded border uppercase tracking-wider ${colorMap[account.cat] || colorMap.Custom}`}> |
| {account.cat.substring(0, 3)} |
| </div> |
| <div className="flex flex-col min-w-0"> |
| <span className="font-bold text-slate-100 truncate text-sm leading-tight">{account.name}</span> |
| <a href={`https://x.com/${account.handle}`} target="_blank" rel="noopener noreferrer" className="text-[11px] text-slate-500 hover:text-cyan-400 truncate flex items-center gap-1 transition-colors font-mono"> |
| @{account.handle} <Icon size={10}>{icons.external}</Icon> |
| </a> |
| </div> |
| </div> |
| <button onClick={() => onRemove(account.handle)} className="text-slate-600 hover:text-red-400 p-2 rounded-lg hover:bg-white/5 transition-all opacity-0 group-hover:opacity-100 focus:opacity-100" title="Remove Feed"> |
| <Icon size={16}>{icons.trash}</Icon> |
| </button> |
| </div> |
| |
| {/* Card Body */} |
| <div className="flex-1 relative bg-black/20 overflow-hidden"> |
| {/* Twitter Container */} |
| <div ref={containerRef} className={`w-full h-full overflow-y-auto twitter-container scrollbar-thin ${status === 'loaded' ? 'opacity-100' : 'opacity-0'} transition-opacity duration-500`}></div> |
| |
| {status === 'loading' && ( |
| <div className="absolute inset-0 pointer-events-none"> |
| <Skeleton /> |
| </div> |
| )} |
| |
| {(status === 'error' || status === 'offline') && ( |
| <div className="absolute inset-0 flex flex-col items-center justify-center p-6 text-center bg-slate-900/90 backdrop-blur-sm z-20"> |
| <div className="p-3 bg-slate-800 rounded-full mb-3 text-slate-400"> |
| <Icon size={24}>{status === 'offline' ? icons.wifiOff : icons.alert}</Icon> |
| </div> |
| <span className="text-slate-300 text-sm font-bold mb-1"> |
| {status === 'offline' ? 'Offline' : 'Feed Unavailable'} |
| </span> |
| <p className="text-xs text-slate-500 mb-4 max-w-[200px]"> |
| {status === 'offline' |
| ? 'Reconnect to internet.' |
| : 'Twitter restricts embedding in this environment (HF/AdBlock).'} |
| </p> |
| <a href={`https://x.com/${account.handle}`} target="_blank" rel="noopener noreferrer" className="px-5 py-2 bg-slate-800 hover:bg-cyan-600 hover:text-black text-cyan-400 text-xs font-bold font-mono rounded-lg transition-all border border-cyan-500/30 flex items-center gap-2"> |
| OPEN ON X.COM <Icon size={12}>{icons.external}</Icon> |
| </a> |
| </div> |
| )} |
| </div> |
| </div> |
| ); |
| }); |
| |
| |
| const App = () => { |
| const [accounts, setAccounts] = useState(() => { |
| try { |
| const saved = localStorage.getItem(STORAGE_KEY); |
| return saved ? JSON.parse(saved) : DEFAULT_ACCOUNTS; |
| } catch { return DEFAULT_ACCOUNTS; } |
| }); |
| const [activeCategory, setActiveCategory] = useState("All"); |
| const [input, setInput] = useState(""); |
| const [online, setOnline] = useState(navigator.onLine); |
| |
| useEffect(() => { |
| |
| window.twttr = (function(d, s, id) { |
| var js, fjs = d.getElementsByTagName(s)[0], |
| t = window.twttr || {}; |
| if (d.getElementById(id)) return t; |
| js = d.createElement(s); |
| js.id = id; |
| js.src = "https://platform.twitter.com/widgets.js"; |
| fjs.parentNode.insertBefore(js, fjs); |
| t._e = []; |
| t.ready = function(f) { |
| t._e.push(f); |
| }; |
| return t; |
| }(document, "script", "twitter-wjs")); |
| |
| const updateOnline = () => setOnline(navigator.onLine); |
| window.addEventListener('online', updateOnline); |
| window.addEventListener('offline', updateOnline); |
| return () => { |
| window.removeEventListener('online', updateOnline); |
| window.removeEventListener('offline', updateOnline); |
| }; |
| }, []); |
| |
| useEffect(() => { |
| localStorage.setItem(STORAGE_KEY, JSON.stringify(accounts)); |
| }, [accounts]); |
| |
| const addAccount = (e) => { |
| e.preventDefault(); |
| if (!input.trim()) return; |
| let handle = input.trim(); |
| |
| try { |
| const url = new URL(handle); |
| handle = url.pathname.split('/').filter(Boolean).pop(); |
| } catch (e) {} |
| |
| handle = handle.replace('@', '').replace('?', '').split('/')[0]; |
| |
| if (accounts.some(a => a.handle.toLowerCase() === handle.toLowerCase())) { |
| alert("Target already monitored."); |
| return; |
| } |
| |
| setAccounts([{ handle, name: handle, cat: "Custom" }, ...accounts]); |
| setInput(""); |
| }; |
| |
| const filteredAccounts = useMemo(() => { |
| if (activeCategory === "All") return accounts; |
| return accounts.filter(acc => acc.cat === activeCategory); |
| }, [accounts, activeCategory]); |
| |
| return ( |
| <div className="min-h-screen flex flex-col font-sans pb-10"> |
| {/* HEADER */} |
| <header className="sticky top-0 z-50 glass-panel border-t-0 border-x-0"> |
| <div className="max-w-[1920px] mx-auto"> |
| <div className="px-4 py-4 flex flex-col md:flex-row md:items-center justify-between gap-4"> |
| <div className="flex items-center gap-4"> |
| <div className="w-10 h-10 rounded-xl bg-gradient-to-br from-cyan-600 to-blue-700 flex items-center justify-center shadow-lg shadow-cyan-900/20"> |
| <Icon size={24} className="text-white">{icons.shield}</Icon> |
| </div> |
| <div> |
| <h1 className="text-xl font-black tracking-tight text-white uppercase flex items-center gap-2"> |
| Crypto Fortress <span className="text-[10px] bg-slate-800 text-slate-400 px-1.5 py-0.5 rounded border border-slate-700">HF-Fixed</span> |
| </h1> |
| <p className="text-xs text-slate-500 font-mono">Real-time Intel Command Center</p> |
| </div> |
| </div> |
| <div className={`flex items-center gap-2 px-3 py-1.5 rounded-full border text-xs font-bold font-mono transition-colors ${online ? 'bg-emerald-950/30 border-emerald-500/30 text-emerald-400' : 'bg-red-950/30 border-red-500/30 text-red-400'}`}> |
| <Icon size={14}>{online ? icons.wifi : icons.wifiOff}</Icon> |
| <span>{online ? 'ONLINE' : 'OFFLINE'}</span> |
| </div> |
| </div> |
| |
| <div className="border-t border-white/5 bg-black/20 px-4 py-2 flex flex-col md:flex-row gap-3 justify-between items-center"> |
| <div className="flex gap-2 overflow-x-auto no-scrollbar w-full md:w-auto pb-1 md:pb-0"> |
| {CATEGORIES.map(cat => ( |
| <button |
| key={cat} |
| onClick={() => setActiveCategory(cat)} |
| className={`px-3 py-1.5 rounded-lg text-xs font-bold font-mono transition-all whitespace-nowrap border ${ |
| activeCategory === cat |
| ? 'bg-slate-800 text-cyan-400 border-cyan-500/50' |
| : 'border-transparent text-slate-500 hover:text-slate-300 hover:bg-white/5' |
| }`} |
| > |
| {cat.toUpperCase()} |
| </button> |
| ))} |
| </div> |
| <form onSubmit={addAccount} className="flex w-full md:w-auto relative group"> |
| <div className="absolute inset-y-0 left-3 flex items-center pointer-events-none text-slate-600"> |
| <Icon size={14}>{icons.plus}</Icon> |
| </div> |
| <input |
| type="text" |
| value={input} |
| onChange={(e) => setInput(e.target.value)} |
| placeholder="Add target handle..." |
| className="w-full md:w-64 bg-slate-900/50 border border-slate-700 text-slate-200 text-xs rounded-l-lg pl-9 pr-3 py-2 focus:outline-none focus:border-cyan-500 font-mono transition-all" |
| /> |
| <button className="bg-slate-800 hover:bg-cyan-700 text-white px-4 py-2 rounded-r-lg text-xs font-bold border border-l-0 border-slate-700"> |
| ADD |
| </button> |
| </form> |
| </div> |
| </div> |
| </header> |
| |
| {/* MAIN GRID */} |
| <main className="flex-1 p-4 md:p-6 w-full max-w-[1920px] mx-auto"> |
| {filteredAccounts.length > 0 ? ( |
| <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-6"> |
| {filteredAccounts.map(acc => ( |
| <TwitterCard |
| key={acc.handle} |
| account={acc} |
| onRemove={(h) => setAccounts(accounts.filter(a => a.handle !== h))} |
| isOnline={online} |
| /> |
| ))} |
| </div> |
| ) : ( |
| <div className="flex flex-col items-center justify-center h-[50vh] text-slate-600"> |
| <div className="p-4 bg-slate-900 rounded-full mb-4"> |
| <Icon size={48} className="opacity-50">{icons.search}</Icon> |
| </div> |
| <h2 className="text-xl font-bold text-slate-500">No Intelligence Found</h2> |
| </div> |
| )} |
| </main> |
| </div> |
| ); |
| }; |
| |
| const root = ReactDOM.createRoot(document.getElementById('root')); |
| root.render(<App />); |
| </script> |
| </body> |
| </html> |