Fortress / index.html
CVNSS's picture
Update index.html
bdb954a verified
<!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.">
<!-- Fix referrer policy for Twitter Widgets -->
<meta name="referrer" content="no-referrer-when-downgrade">
<!-- Favicon SVG -->
<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>">
<!-- Tailwind CSS -->
<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>
<!-- React Dependencies -->
<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; }
/* Force twitter widget container height */
.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;
// ================== ICONS ==================
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"/></>
};
// ================== DATA ==================
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';
// ================== COMPONENTS ==================
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>
);
// Improved Twitter Card specifically for Hugging Face / Strict Environments
const TwitterCard = React.memo(({ account, onRemove, isOnline }) => {
const containerRef = useRef(null);
const [status, setStatus] = useState('loading'); // loading | loaded | error | offline
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 = ''; // Clean up
// Use createTimeline instead of scanning DOM (More reliable for React/SPA)
window.twttr.widgets.createTimeline(
{
sourceType: 'profile',
screenName: account.handle
},
container,
{
theme: 'dark',
height: 400,
chrome: 'noheader,nofooter,noborders,transparent,noscrollbar',
dnt: true // Do Not Track - helps with privacy blockers
}
).then((el) => {
if (isMounted) {
if (el) {
setStatus('loaded');
} else {
// Widget returned null (Blocked or handle invalid)
setStatus('error');
}
}
}).catch(() => {
if (isMounted) setStatus('error');
});
};
// Observer for lazy loading
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
initWidget();
observer.disconnect();
}
}, { rootMargin: '100px' });
if (container) observer.observe(container);
// Timeout Safety: If widget takes too long (e.g. Hugging Face blocks it), show error/link
const timer = setTimeout(() => {
if (isMounted && status === 'loading') {
setStatus('error');
}
}, 4000); // 4 seconds timeout
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>
);
});
// ================== MAIN APP ==================
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(() => {
// Initialize Twitter Widget Script Globally
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();
// Clean URL inputs
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>