mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-06-07 14:53:53 +02:00
fix: bump text sizes across all mesh/infonet/settings components
7px→11px, 8px→12px, 9px→13px, 10px→14px (text-sm) across MeshChat, MeshTerminal, InfonetTerminal (all sub-components), ShodanPanel, SettingsPanel, and OnboardingModal. 316 instances total.
This commit is contained in:
@@ -40,7 +40,7 @@ export default function BallotView({ onBack }: { onBack: () => void }) {
|
||||
|
||||
<div className="mt-6 grid gap-4 md:grid-cols-3">
|
||||
<div className="border border-gray-800 bg-black/20 p-4">
|
||||
<div className="text-[10px] text-cyan-400 uppercase tracking-[0.22em]">
|
||||
<div className="text-sm text-cyan-400 uppercase tracking-[0.22em]">
|
||||
Principle
|
||||
</div>
|
||||
<div className="mt-2 text-sm text-gray-300 leading-relaxed">
|
||||
@@ -48,7 +48,7 @@ export default function BallotView({ onBack }: { onBack: () => void }) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="border border-gray-800 bg-black/20 p-4">
|
||||
<div className="text-[10px] text-cyan-400 uppercase tracking-[0.22em]">
|
||||
<div className="text-sm text-cyan-400 uppercase tracking-[0.22em]">
|
||||
Current stance
|
||||
</div>
|
||||
<div className="mt-2 text-sm text-gray-300 leading-relaxed">
|
||||
@@ -56,7 +56,7 @@ export default function BallotView({ onBack }: { onBack: () => void }) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="border border-gray-800 bg-black/20 p-4">
|
||||
<div className="text-[10px] text-cyan-400 uppercase tracking-[0.22em]">
|
||||
<div className="text-sm text-cyan-400 uppercase tracking-[0.22em]">
|
||||
Testnet focus
|
||||
</div>
|
||||
<div className="mt-2 text-sm text-gray-300 leading-relaxed">
|
||||
|
||||
@@ -524,7 +524,7 @@ export default function GateView({
|
||||
</div>
|
||||
<button
|
||||
onClick={() => void refreshGate()}
|
||||
className="inline-flex items-center gap-2 px-3 py-2 border border-cyan-500/30 bg-cyan-950/20 text-cyan-300 hover:bg-cyan-900/30 transition-colors text-[10px] uppercase tracking-[0.22em]"
|
||||
className="inline-flex items-center gap-2 px-3 py-2 border border-cyan-500/30 bg-cyan-950/20 text-cyan-300 hover:bg-cyan-900/30 transition-colors text-sm uppercase tracking-[0.22em]"
|
||||
>
|
||||
<RefreshCw size={13} />
|
||||
Refresh
|
||||
@@ -544,7 +544,7 @@ export default function GateView({
|
||||
: 'Saved gate face is active for this room. Posts stay scoped to this gate while the room history persists on the obfuscated gate lane.'}
|
||||
</div>
|
||||
|
||||
<div className="mt-3 text-[10px] font-mono text-cyan-400/85">
|
||||
<div className="mt-3 text-sm font-mono text-cyan-400/85">
|
||||
{status?.has_local_access
|
||||
? `LIVE ROOM READY • ${status.identity_scope || entryMode || 'gate'} access`
|
||||
: loading
|
||||
@@ -588,7 +588,7 @@ export default function GateView({
|
||||
</div>
|
||||
) : null}
|
||||
{voteNotice ? (
|
||||
<div className="mb-2 shrink-0 border border-yellow-800/30 bg-yellow-950/10 px-3 py-1.5 text-[10px] text-yellow-400/80 font-mono">
|
||||
<div className="mb-2 shrink-0 border border-yellow-800/30 bg-yellow-950/10 px-3 py-1.5 text-sm text-yellow-400/80 font-mono">
|
||||
{voteNotice}
|
||||
</div>
|
||||
) : null}
|
||||
@@ -604,7 +604,7 @@ export default function GateView({
|
||||
<span className="text-gray-600 ml-2">PINNED</span>
|
||||
</div>
|
||||
<h2 className="text-sm md:text-base text-gray-300 leading-relaxed">{introMessage}</h2>
|
||||
<div className="mt-3 pt-2 border-t border-gray-800/50 text-[10px] text-amber-400/70 tracking-wider uppercase">
|
||||
<div className="mt-3 pt-2 border-t border-gray-800/50 text-sm text-amber-400/70 tracking-wider uppercase">
|
||||
Fixed launch gate for the testnet catalog. Dynamic gate creation is disabled.
|
||||
</div>
|
||||
</div>
|
||||
@@ -613,10 +613,10 @@ export default function GateView({
|
||||
{threadedMessages.map(({ message, depth }) =>
|
||||
message.system_seed ? (
|
||||
<div key={message.event_id} className="border border-cyan-900/30 bg-cyan-950/10 px-3 py-3 max-w-3xl">
|
||||
<div className="text-[8px] font-mono tracking-[0.28em] text-cyan-300/85">
|
||||
<div className="text-[12px] font-mono tracking-[0.28em] text-cyan-300/85">
|
||||
{message.fixed_gate ? 'FIXED GATE NOTICE' : 'GATE NOTICE'}
|
||||
</div>
|
||||
<div className="mt-2 text-[10px] font-mono text-cyan-100/80 leading-[1.7]">
|
||||
<div className="mt-2 text-sm font-mono text-cyan-100/80 leading-[1.7]">
|
||||
{message.message}
|
||||
</div>
|
||||
</div>
|
||||
@@ -632,7 +632,7 @@ export default function GateView({
|
||||
<div className={`flex-1 border ${depth > 0 ? 'border-gray-800/40 bg-black/10' : 'border-gray-800/70 bg-black/20'} px-3 py-3`}>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2 text-[10px] font-mono">
|
||||
<div className="flex items-center gap-2 text-sm font-mono">
|
||||
<span className="text-green-400" title={String(message.public_key || message.node_id || '')}>
|
||||
@{String(message.node_id || '').replace(/^!sb_/, '').slice(0, 8)
|
||||
|| String(message.public_key || '').slice(0, 8)
|
||||
@@ -640,7 +640,7 @@ export default function GateView({
|
||||
</span>
|
||||
{isEncryptedGateEnvelope(message) ? (
|
||||
<span
|
||||
className={`text-[8px] px-1 border ${
|
||||
className={`text-[12px] px-1 border ${
|
||||
gateEnvelopeState(message) === 'decrypted'
|
||||
? 'text-cyan-300 border-cyan-700/60'
|
||||
: 'text-amber-300 border-amber-700/60'
|
||||
@@ -649,7 +649,7 @@ export default function GateView({
|
||||
{gateEnvelopeState(message) === 'decrypted' ? 'DECRYPTED' : 'KEY LOCKED'}
|
||||
</span>
|
||||
) : null}
|
||||
<span className="text-[var(--text-muted)] text-[9px]">{timeAgo(message.timestamp)}</span>
|
||||
<span className="text-[var(--text-muted)] text-[13px]">{timeAgo(message.timestamp)}</span>
|
||||
</div>
|
||||
<div
|
||||
className={`mt-2 text-[12px] leading-[1.7] whitespace-pre-wrap break-words ${
|
||||
@@ -668,7 +668,7 @@ export default function GateView({
|
||||
nodeId: String(message.node_id || ''),
|
||||
})
|
||||
}
|
||||
className="inline-flex items-center gap-1 px-2 py-1 text-[9px] uppercase tracking-[0.18em] border border-cyan-900/40 text-cyan-400 hover:bg-cyan-950/20"
|
||||
className="inline-flex items-center gap-1 px-2 py-1 text-[13px] uppercase tracking-[0.18em] border border-cyan-900/40 text-cyan-400 hover:bg-cyan-950/20"
|
||||
>
|
||||
<Reply size={11} />
|
||||
Reply
|
||||
@@ -677,7 +677,7 @@ export default function GateView({
|
||||
<>
|
||||
<button
|
||||
onClick={() => void handleVote(String(message.event_id || ''), 1)}
|
||||
className={`inline-flex items-center gap-1 px-2 py-1 text-[9px] uppercase tracking-[0.18em] border ${
|
||||
className={`inline-flex items-center gap-1 px-2 py-1 text-[13px] uppercase tracking-[0.18em] border ${
|
||||
votedOn[voteScopeKey(String(message.event_id || ''))] === 1
|
||||
? 'border-cyan-400/60 text-cyan-300 bg-cyan-950/20'
|
||||
: 'border-cyan-900/40 text-cyan-500 hover:bg-cyan-950/20'
|
||||
@@ -688,7 +688,7 @@ export default function GateView({
|
||||
</button>
|
||||
<button
|
||||
onClick={() => void handleVote(String(message.event_id || ''), -1)}
|
||||
className={`inline-flex items-center gap-1 px-2 py-1 text-[9px] uppercase tracking-[0.18em] border ${
|
||||
className={`inline-flex items-center gap-1 px-2 py-1 text-[13px] uppercase tracking-[0.18em] border ${
|
||||
votedOn[voteScopeKey(String(message.event_id || ''))] === -1
|
||||
? 'border-red-400/60 text-red-300 bg-red-950/20'
|
||||
: 'border-cyan-900/40 text-red-400 hover:bg-red-950/20'
|
||||
@@ -697,7 +697,7 @@ export default function GateView({
|
||||
<ArrowDown size={11} />
|
||||
Down
|
||||
</button>
|
||||
<span className="text-[10px] font-mono text-cyan-400/70">
|
||||
<span className="text-sm font-mono text-cyan-400/70">
|
||||
SCORE {(() => { const s = reps[String(message.event_id || '')] ?? 0; return s % 1 === 0 ? s : s.toFixed(1); })()}
|
||||
</span>
|
||||
</>
|
||||
@@ -714,7 +714,7 @@ export default function GateView({
|
||||
|
||||
<div className="shrink-0 pt-3 mt-2 border-t border-gray-800/50">
|
||||
{replyContext ? (
|
||||
<div className="mb-2 flex items-center justify-between gap-2 border border-amber-900/30 bg-amber-950/10 px-3 py-2 text-[10px] text-amber-200/80">
|
||||
<div className="mb-2 flex items-center justify-between gap-2 border border-amber-900/30 bg-amber-950/10 px-3 py-2 text-sm text-amber-200/80">
|
||||
<span>
|
||||
Replying to @{replyContext.eventId.slice(0, 8)}
|
||||
</span>
|
||||
@@ -745,7 +745,7 @@ export default function GateView({
|
||||
<button
|
||||
onClick={() => void handleSend()}
|
||||
disabled={busy || !composer.trim() || !status?.has_local_access}
|
||||
className="inline-flex items-center gap-2 px-4 py-3 border border-cyan-500/40 bg-cyan-950/20 text-cyan-300 hover:bg-cyan-900/30 transition-colors text-[10px] uppercase tracking-[0.22em] disabled:opacity-40"
|
||||
className="inline-flex items-center gap-2 px-4 py-3 border border-cyan-500/40 bg-cyan-950/20 text-cyan-300 hover:bg-cyan-900/30 transition-colors text-sm uppercase tracking-[0.22em] disabled:opacity-40"
|
||||
>
|
||||
<Send size={13} />
|
||||
Post
|
||||
|
||||
@@ -34,17 +34,17 @@ export default function HashchainEvents() {
|
||||
{ROADMAP_ITEMS.map((item, i) => (
|
||||
<div key={i} className="group cursor-pointer">
|
||||
<div className="flex justify-between items-center mb-0.5">
|
||||
<span className="text-[10px] text-green-400 uppercase tracking-widest border border-gray-800 px-1">
|
||||
<span className="text-sm text-green-400 uppercase tracking-widest border border-gray-800 px-1">
|
||||
{item.type}
|
||||
</span>
|
||||
<span className="text-[10px] font-bold text-cyan-400">
|
||||
<span className="text-sm font-bold text-cyan-400">
|
||||
{item.status}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-300 group-hover:text-white transition-colors mt-1">
|
||||
{item.title}
|
||||
</p>
|
||||
<div className="text-[10px] text-gray-500 mt-1 leading-relaxed">
|
||||
<div className="text-sm text-gray-500 mt-1 leading-relaxed">
|
||||
{item.detail}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -30,7 +30,7 @@ export default function IdentityHUD({ currentDomain = 'TRANSPORT' }: { currentDo
|
||||
{isExpanded && (
|
||||
<div className="mb-2 w-64 bg-[#0a0a0a] border border-gray-800 p-3 shadow-[0_0_20px_rgba(6,182,212,0.1)]">
|
||||
<div className="flex justify-between items-center mb-3 border-b border-gray-800 pb-2">
|
||||
<span className="text-[10px] text-gray-500 uppercase tracking-widest font-bold">Identity Domains</span>
|
||||
<span className="text-sm text-gray-500 uppercase tracking-widest font-bold">Identity Domains</span>
|
||||
<button onClick={() => setIsExpanded(false)} className="text-gray-500 hover:text-white">×</button>
|
||||
</div>
|
||||
|
||||
@@ -43,8 +43,8 @@ export default function IdentityHUD({ currentDomain = 'TRANSPORT' }: { currentDo
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={isActive ? 'text-cyan-400' : 'text-gray-600'}>{d.icon}</span>
|
||||
<div>
|
||||
<p className={`text-[10px] font-bold tracking-tighter ${isActive ? 'text-white' : 'text-gray-500'}`}>{d.name}</p>
|
||||
<p className="text-[8px] text-gray-600 uppercase">{d.visibility}</p>
|
||||
<p className={`text-sm font-bold tracking-tighter ${isActive ? 'text-white' : 'text-gray-500'}`}>{d.name}</p>
|
||||
<p className="text-[12px] text-gray-600 uppercase">{d.visibility}</p>
|
||||
</div>
|
||||
</div>
|
||||
{isActive && (
|
||||
@@ -63,7 +63,7 @@ export default function IdentityHUD({ currentDomain = 'TRANSPORT' }: { currentDo
|
||||
</div>
|
||||
|
||||
<div className="mt-3 pt-2 border-t border-gray-800">
|
||||
<p className="text-[8px] text-red-500/70 uppercase leading-tight">
|
||||
<p className="text-[12px] text-red-500/70 uppercase leading-tight">
|
||||
CRITICAL: CROSS-DOMAIN LINKAGE IS PROTOCOL-FORBIDDEN.
|
||||
ROTATING IDENTITY PURGES ALL LOCAL SESSION CACHE.
|
||||
</p>
|
||||
@@ -76,7 +76,7 @@ export default function IdentityHUD({ currentDomain = 'TRANSPORT' }: { currentDo
|
||||
className={`flex items-center gap-3 px-4 py-2 border ${isExpanded ? 'border-cyan-500 bg-cyan-900/20' : 'border-gray-800 bg-gray-900/80'} backdrop-blur-md transition-all hover:border-cyan-400 group`}
|
||||
>
|
||||
<div className="flex flex-col items-end">
|
||||
<span className="text-[10px] text-gray-500 uppercase tracking-widest leading-none mb-1">Active Domain</span>
|
||||
<span className="text-sm text-gray-500 uppercase tracking-widest leading-none mb-1">Active Domain</span>
|
||||
<span className={`text-xs font-bold tracking-widest ${domain.color} flex items-center gap-1`}>
|
||||
{domain.icon} {domain.name}
|
||||
</span>
|
||||
|
||||
@@ -496,7 +496,7 @@ export default function InfonetShell({ isOpen, onClose, onOpenLiveGate }: Infone
|
||||
<button
|
||||
key={section.name}
|
||||
onClick={() => handleCommand(section.name === 'PROFILE' ? 'profile' : section.name.toLowerCase())}
|
||||
className="flex items-center px-2 py-1 bg-cyan-900/10 border border-cyan-900/50 text-cyan-500 hover:bg-cyan-900/30 hover:text-cyan-400 hover:border-cyan-500/50 transition-all text-[10px] md:text-xs uppercase tracking-widest whitespace-nowrap"
|
||||
className="flex items-center px-2 py-1 bg-cyan-900/10 border border-cyan-900/50 text-cyan-500 hover:bg-cyan-900/30 hover:text-cyan-400 hover:border-cyan-500/50 transition-all text-sm md:text-xs uppercase tracking-widest whitespace-nowrap"
|
||||
>
|
||||
{section.icon}
|
||||
{section.name === 'PROFILE' ? 'SOVEREIGN' : section.name}
|
||||
@@ -513,7 +513,7 @@ export default function InfonetShell({ isOpen, onClose, onOpenLiveGate }: Infone
|
||||
|
||||
<div className="flex-1 flex flex-col items-center">
|
||||
<pre
|
||||
className="text-white drop-shadow-[0_0_8px_rgba(156,163,175,0.8)] text-[10px] sm:text-xs md:text-sm leading-tight select-none text-left inline-block"
|
||||
className="text-white drop-shadow-[0_0_8px_rgba(156,163,175,0.8)] text-sm sm:text-xs md:text-sm leading-tight select-none text-left inline-block"
|
||||
style={{ fontFamily: 'Consolas, "Courier New", monospace' }}
|
||||
>
|
||||
{ASCII_HEADER}
|
||||
@@ -623,7 +623,7 @@ export default function InfonetShell({ isOpen, onClose, onOpenLiveGate }: Infone
|
||||
<div className="flex items-center justify-between px-4 py-2 border-b border-cyan-900/40 bg-cyan-950/20">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-amber-500 animate-pulse shadow-[0_0_6px_rgba(245,158,11,0.6)]" />
|
||||
<span className="text-[9px] tracking-[0.3em] text-amber-400/80 uppercase">System Notice</span>
|
||||
<span className="text-[13px] tracking-[0.3em] text-amber-400/80 uppercase">System Notice</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setComingSoonModule(null)}
|
||||
@@ -647,18 +647,18 @@ export default function InfonetShell({ isOpen, onClose, onOpenLiveGate }: Infone
|
||||
|
||||
<div className="flex items-center gap-2 mb-4 px-1">
|
||||
<span className="w-1 h-1 rounded-full bg-amber-500 animate-pulse" />
|
||||
<span className="text-[9px] tracking-[0.2em] text-amber-400/90 uppercase">
|
||||
<span className="text-[13px] tracking-[0.2em] text-amber-400/90 uppercase">
|
||||
{COMING_SOON_MODULES[comingSoonModule].status}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-gray-800 pt-4 flex items-center justify-between">
|
||||
<span className="text-[8px] text-gray-600 tracking-[0.2em] uppercase">
|
||||
<span className="text-[12px] text-gray-600 tracking-[0.2em] uppercase">
|
||||
Infonet Sovereign Shell v0.1.1 — Test-Net
|
||||
</span>
|
||||
<button
|
||||
onClick={() => setComingSoonModule(null)}
|
||||
className="px-4 py-1.5 border border-cyan-900/50 bg-cyan-950/20 text-cyan-400 text-[10px] tracking-[0.2em] uppercase hover:bg-cyan-900/30 hover:border-cyan-500/40 transition-all"
|
||||
className="px-4 py-1.5 border border-cyan-900/50 bg-cyan-950/20 text-cyan-400 text-sm tracking-[0.2em] uppercase hover:bg-cyan-900/30 hover:border-cyan-500/40 transition-all"
|
||||
>
|
||||
Acknowledged
|
||||
</button>
|
||||
|
||||
@@ -198,14 +198,14 @@ export default function LiveActivityLog() {
|
||||
<Activity size={14} className="mr-2 animate-pulse text-green-400" />
|
||||
Live Network Telemetry
|
||||
</h3>
|
||||
<span className="text-[10px] text-gray-500 font-mono">
|
||||
<span className="text-sm text-gray-500 font-mono">
|
||||
FEEDS: {logs.length} EVENTS
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref={scrollRef}
|
||||
className="flex-1 overflow-y-auto font-mono text-[10px] sm:text-xs space-y-1.5 pr-2 [&::-webkit-scrollbar]:w-1 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:bg-gray-800"
|
||||
className="flex-1 overflow-y-auto font-mono text-sm sm:text-xs space-y-1.5 pr-2 [&::-webkit-scrollbar]:w-1 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:bg-gray-800"
|
||||
>
|
||||
{logs.length === 0 && (
|
||||
<div className="text-gray-600 italic text-center py-4">Waiting for data stream...</div>
|
||||
|
||||
@@ -107,7 +107,7 @@ export default function MarketView({ onBack }: MarketViewProps) {
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<span className="text-[10px] text-gray-500 font-mono">{filteredMarkets.length} RESULTS</span>
|
||||
<span className="text-sm text-gray-500 font-mono">{filteredMarkets.length} RESULTS</span>
|
||||
</div>
|
||||
|
||||
{/* Search Bar */}
|
||||
@@ -144,7 +144,7 @@ export default function MarketView({ onBack }: MarketViewProps) {
|
||||
<div className="flex items-start justify-between gap-4 mb-3">
|
||||
<div className="flex-1">
|
||||
<div className="text-gray-300 font-bold text-sm md:text-base leading-snug">{market.title}</div>
|
||||
<div className="flex items-center gap-2 mt-1.5 text-[10px] font-mono">
|
||||
<div className="flex items-center gap-2 mt-1.5 text-sm font-mono">
|
||||
<span className={`${catConfig.color} uppercase tracking-widest`}>{market.category}</span>
|
||||
{vol && <span className="text-gray-500">VOL: {vol}</span>}
|
||||
{vol24 && <span className="text-gray-500">24H: {vol24}</span>}
|
||||
@@ -155,12 +155,12 @@ export default function MarketView({ onBack }: MarketViewProps) {
|
||||
{outcomes && outcomes.length > 0 ? (
|
||||
<>
|
||||
<div className="text-2xl font-bold text-cyan-400 font-mono">{outcomes[0].pct}%</div>
|
||||
<div className="text-[9px] text-gray-400 uppercase truncate max-w-[100px]" title={outcomes[0].name}>{outcomes[0].name}</div>
|
||||
<div className="text-[13px] text-gray-400 uppercase truncate max-w-[100px]" title={outcomes[0].name}>{outcomes[0].name}</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="text-2xl font-bold text-emerald-400 font-mono">{pct}%</div>
|
||||
<div className="text-[9px] text-gray-500 uppercase">CONSENSUS</div>
|
||||
<div className="text-[13px] text-gray-500 uppercase">CONSENSUS</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -169,21 +169,21 @@ export default function MarketView({ onBack }: MarketViewProps) {
|
||||
{/* Probability bar */}
|
||||
{outcomes && outcomes.length > 0 ? (
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<span className="text-[9px] text-cyan-400 font-mono truncate max-w-[80px]" title={outcomes[0].name}>{outcomes[0].name}</span>
|
||||
<span className="text-[13px] text-cyan-400 font-mono truncate max-w-[80px]" title={outcomes[0].name}>{outcomes[0].name}</span>
|
||||
<div className="flex-1 h-2 bg-gray-900 overflow-hidden flex">
|
||||
<div className="bg-cyan-500/60" style={{ width: `${outcomes[0].pct}%` }} />
|
||||
<div className="bg-gray-700/30 flex-1" />
|
||||
</div>
|
||||
<span className="text-[9px] text-cyan-400 font-mono w-8 text-right">{outcomes[0].pct}%</span>
|
||||
<span className="text-[13px] text-cyan-400 font-mono w-8 text-right">{outcomes[0].pct}%</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<span className="text-[9px] text-green-400 font-mono w-8">YES</span>
|
||||
<span className="text-[13px] text-green-400 font-mono w-8">YES</span>
|
||||
<div className="flex-1 h-2 bg-gray-900 overflow-hidden flex">
|
||||
<div className="bg-emerald-500/60" style={{ width: `${pct}%` }} />
|
||||
<div className="bg-red-500/30 flex-1" />
|
||||
</div>
|
||||
<span className="text-[9px] text-red-400 font-mono w-8 text-right">NO</span>
|
||||
<span className="text-[13px] text-red-400 font-mono w-8 text-right">NO</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -191,7 +191,7 @@ export default function MarketView({ onBack }: MarketViewProps) {
|
||||
<div className="flex items-center justify-between flex-wrap gap-2">
|
||||
<div className="flex items-center gap-1.5 flex-wrap">
|
||||
{market.sources?.map((s, si) => (
|
||||
<span key={si} className={`text-[9px] font-mono px-1.5 py-0.5 border ${
|
||||
<span key={si} className={`text-[13px] font-mono px-1.5 py-0.5 border ${
|
||||
s.name === 'POLY'
|
||||
? 'bg-purple-500/15 text-purple-400 border-purple-500/20'
|
||||
: 'bg-blue-500/15 text-blue-400 border-blue-500/20'
|
||||
@@ -200,7 +200,7 @@ export default function MarketView({ onBack }: MarketViewProps) {
|
||||
</span>
|
||||
))}
|
||||
{consensus && consensus.total_picks > 0 && (
|
||||
<span className="text-[9px] font-mono px-1.5 py-0.5 border bg-amber-500/10 text-amber-400 border-amber-500/20">
|
||||
<span className="text-[13px] font-mono px-1.5 py-0.5 border bg-amber-500/10 text-amber-400 border-amber-500/20">
|
||||
{consensus.total_picks} pick{consensus.total_picks !== 1 ? 's' : ''}
|
||||
{consensus.total_staked > 0 ? ` · ${consensus.total_staked.toFixed(1)} REP` : ''}
|
||||
</span>
|
||||
@@ -209,7 +209,7 @@ export default function MarketView({ onBack }: MarketViewProps) {
|
||||
|
||||
{/* Delta indicator */}
|
||||
{market.delta_pct != null && market.delta_pct !== 0 && (
|
||||
<span className={`text-[10px] font-mono font-bold ${market.delta_pct > 0 ? 'text-green-400' : 'text-red-400'}`}>
|
||||
<span className={`text-sm font-mono font-bold ${market.delta_pct > 0 ? 'text-green-400' : 'text-red-400'}`}>
|
||||
{market.delta_pct > 0 ? '▲' : '▼'} {Math.abs(market.delta_pct).toFixed(1)}%
|
||||
</span>
|
||||
)}
|
||||
@@ -219,7 +219,7 @@ export default function MarketView({ onBack }: MarketViewProps) {
|
||||
{outcomes && outcomes.length > 0 && (
|
||||
<div className="mt-3 pt-2 border-t border-gray-800 space-y-1">
|
||||
{outcomes.slice(0, 5).map((outcome, oi) => (
|
||||
<div key={oi} className="flex items-center gap-2 text-[10px]">
|
||||
<div key={oi} className="flex items-center gap-2 text-sm">
|
||||
<span className="text-gray-400 w-24 truncate">{outcome.name}</span>
|
||||
<div className="flex-1 h-1 bg-gray-900 overflow-hidden">
|
||||
<div className="bg-cyan-500/50 h-full" style={{ width: `${outcome.pct}%` }} />
|
||||
|
||||
@@ -1369,7 +1369,7 @@ export default function MessagesView({ onBack }: MessagesViewProps) {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => void refreshMailbox()}
|
||||
className="flex items-center text-cyan-400 hover:text-cyan-300 uppercase text-[10px] tracking-[0.2em] border border-cyan-900/50 px-3 py-1 bg-cyan-900/10 disabled:opacity-50"
|
||||
className="flex items-center text-cyan-400 hover:text-cyan-300 uppercase text-sm tracking-[0.2em] border border-cyan-900/50 px-3 py-1 bg-cyan-900/10 disabled:opacity-50"
|
||||
disabled={!identity || syncing || !dmLaneReady}
|
||||
>
|
||||
<RefreshCcw size={13} className={`mr-2 ${syncing ? 'animate-spin' : ''}`} />
|
||||
@@ -1474,7 +1474,7 @@ export default function MessagesView({ onBack }: MessagesViewProps) {
|
||||
<div className="text-cyan-300 text-sm mb-1">{mail.subject}</div>
|
||||
<div className="text-xs text-gray-500 line-clamp-2">{messagePreview(mail)}</div>
|
||||
{!mail.read && (
|
||||
<div className="mt-2 text-[10px] tracking-[0.2em] uppercase text-cyan-400">
|
||||
<div className="mt-2 text-sm tracking-[0.2em] uppercase text-cyan-400">
|
||||
unread
|
||||
</div>
|
||||
)}
|
||||
@@ -1498,7 +1498,7 @@ export default function MessagesView({ onBack }: MessagesViewProps) {
|
||||
{formatTimestamp(selectedMessage.timestamp)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-[10px] tracking-[0.18em] uppercase text-gray-500">
|
||||
<div className="text-sm tracking-[0.18em] uppercase text-gray-500">
|
||||
{selectedMessage.transport || 'local'}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1681,7 +1681,7 @@ export default function MessagesView({ onBack }: MessagesViewProps) {
|
||||
setActiveTab('compose');
|
||||
}}
|
||||
disabled={!dmLaneReady}
|
||||
className="px-3 py-2 border border-cyan-500/30 text-cyan-300 text-[10px] tracking-[0.18em] uppercase disabled:opacity-50"
|
||||
className="px-3 py-2 border border-cyan-500/30 text-cyan-300 text-sm tracking-[0.18em] uppercase disabled:opacity-50"
|
||||
>
|
||||
Compose
|
||||
</button>
|
||||
@@ -1690,7 +1690,7 @@ export default function MessagesView({ onBack }: MessagesViewProps) {
|
||||
blockContact(peerId);
|
||||
setContacts(getContacts());
|
||||
}}
|
||||
className="px-3 py-2 border border-amber-500/30 text-amber-300 text-[10px] tracking-[0.18em] uppercase"
|
||||
className="px-3 py-2 border border-amber-500/30 text-amber-300 text-sm tracking-[0.18em] uppercase"
|
||||
>
|
||||
Restrict
|
||||
</button>
|
||||
@@ -1699,7 +1699,7 @@ export default function MessagesView({ onBack }: MessagesViewProps) {
|
||||
removeContact(peerId);
|
||||
setContacts(getContacts());
|
||||
}}
|
||||
className="px-3 py-2 border border-red-500/30 text-red-300 text-[10px] tracking-[0.18em] uppercase"
|
||||
className="px-3 py-2 border border-red-500/30 text-red-300 text-sm tracking-[0.18em] uppercase"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
@@ -1764,7 +1764,7 @@ export default function MessagesView({ onBack }: MessagesViewProps) {
|
||||
unblockContact(peerId);
|
||||
setContacts(getContacts());
|
||||
}}
|
||||
className="px-4 py-2 border border-cyan-500/40 bg-cyan-950/20 text-cyan-300 text-[10px] tracking-[0.18em] uppercase"
|
||||
className="px-4 py-2 border border-cyan-500/40 bg-cyan-950/20 text-cyan-300 text-sm tracking-[0.18em] uppercase"
|
||||
>
|
||||
Restore
|
||||
</button>
|
||||
|
||||
@@ -55,7 +55,7 @@ export default function NetworkStats() {
|
||||
: stats.nodeEnabled ? 'SYNCING' : 'OFFLINE';
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center justify-center gap-x-5 gap-y-1 mt-5 text-[10px] font-mono text-gray-500">
|
||||
<div className="flex flex-wrap items-center justify-center gap-x-5 gap-y-1 mt-5 text-sm font-mono text-gray-500">
|
||||
<span>NODE <span className={nodeColor}>{nodeLabel}</span></span>
|
||||
<span className="text-gray-700">|</span>
|
||||
<span>MESH <span className={stats.meshtastic > 0 ? 'text-green-400' : 'text-gray-600'}>{stats.meshtastic.toLocaleString()}</span></span>
|
||||
|
||||
@@ -187,11 +187,11 @@ export default function ProfileView({ onBack, persona, isCitizen, nodeId, public
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4 text-right">
|
||||
<div>
|
||||
<p className="text-[10px] text-gray-500 uppercase tracking-widest">Lit</p>
|
||||
<p className="text-sm text-gray-500 uppercase tracking-widest">Lit</p>
|
||||
<p className="text-lg font-bold text-green-400">{upvotes}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-[10px] text-gray-500 uppercase tracking-widest">Dislikes</p>
|
||||
<p className="text-sm text-gray-500 uppercase tracking-widest">Dislikes</p>
|
||||
<p className="text-lg font-bold text-red-400">{downvotes}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -202,21 +202,21 @@ export default function ProfileView({ onBack, persona, isCitizen, nodeId, public
|
||||
style={{ width: `${repProgress}%` }}
|
||||
/>
|
||||
</div>
|
||||
<p className="mt-2 text-[10px] text-gray-500 uppercase tracking-tighter">
|
||||
<p className="mt-2 text-sm text-gray-500 uppercase tracking-tighter">
|
||||
Reputation is derived from live lit/dislike activity. Net rep can drop below zero even when the bar is clamped at zero.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 md:col-span-2 mt-2">
|
||||
<div className="p-3 bg-gray-900/40 border border-gray-800">
|
||||
<p className="text-[10px] text-gray-500 uppercase tracking-widest">Active Months</p>
|
||||
<p className="text-sm text-gray-500 uppercase tracking-widest">Active Months</p>
|
||||
<p className="text-xl text-white font-bold">0 MONTHS</p>
|
||||
<p className="text-[9px] text-gray-600 mt-1 uppercase">No live citizenship accounting yet</p>
|
||||
<p className="text-[13px] text-gray-600 mt-1 uppercase">No live citizenship accounting yet</p>
|
||||
</div>
|
||||
<div className="p-3 bg-gray-900/40 border border-gray-800">
|
||||
<p className="text-[10px] text-gray-500 uppercase tracking-widest">Citizenship History</p>
|
||||
<p className="text-sm text-gray-500 uppercase tracking-widest">Citizenship History</p>
|
||||
<p className="text-xl text-gray-400 font-bold">0 MONTHS</p>
|
||||
<p className="text-[9px] text-gray-600 mt-1 uppercase">Placeholder totals removed</p>
|
||||
<p className="text-[13px] text-gray-600 mt-1 uppercase">Placeholder totals removed</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -227,7 +227,7 @@ export default function ProfileView({ onBack, persona, isCitizen, nodeId, public
|
||||
<p className="text-xl text-cyan-400 font-bold">
|
||||
{oracleRep.toFixed(1)} <span className="text-xs text-gray-500 font-normal">AVAILABLE</span>
|
||||
</p>
|
||||
<p className="text-[10px] text-gray-500 uppercase">
|
||||
<p className="text-sm text-gray-500 uppercase">
|
||||
Win Rate {oracleProfile.win_rate}% • W {oracleProfile.predictions_won} / L {oracleProfile.predictions_lost}
|
||||
</p>
|
||||
</div>
|
||||
@@ -237,7 +237,7 @@ export default function ProfileView({ onBack, persona, isCitizen, nodeId, public
|
||||
style={{ width: `${oracleProgress}%` }}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-[10px] text-gray-500 uppercase tracking-tighter">
|
||||
<p className="text-sm text-gray-500 uppercase tracking-tighter">
|
||||
Available: {oracleRep.toFixed(1)} | Locked: {oracleRepLocked.toFixed(1)} | Total: {oracleRepTotal.toFixed(1)}
|
||||
</p>
|
||||
</div>
|
||||
@@ -251,7 +251,7 @@ export default function ProfileView({ onBack, persona, isCitizen, nodeId, public
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div className="flex flex-col items-center justify-center p-4 border border-gray-800 bg-[#0a0a0a]">
|
||||
<p className="text-[10px] text-gray-500 uppercase tracking-widest mb-2">Vote Correlation</p>
|
||||
<p className="text-sm text-gray-500 uppercase tracking-widest mb-2">Vote Correlation</p>
|
||||
<div className="relative h-20 w-20">
|
||||
<svg className="h-full w-full" viewBox="0 0 36 36">
|
||||
<path className="stroke-gray-800 stroke-[3]" fill="none" d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" />
|
||||
@@ -261,14 +261,14 @@ export default function ProfileView({ onBack, persona, isCitizen, nodeId, public
|
||||
<span className="text-sm font-bold text-gray-400">0.00</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-[8px] text-gray-500 mt-2 uppercase">NOT CALIBRATED</p>
|
||||
<p className="text-[12px] text-gray-500 mt-2 uppercase">NOT CALIBRATED</p>
|
||||
</div>
|
||||
|
||||
<div className="md:col-span-2 space-y-4">
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb-1">
|
||||
<p className="text-[10px] text-gray-500 uppercase tracking-widest">Clustering Coefficient</p>
|
||||
<p className="text-[10px] text-gray-400 font-bold">0.00</p>
|
||||
<p className="text-sm text-gray-500 uppercase tracking-widest">Clustering Coefficient</p>
|
||||
<p className="text-sm text-gray-400 font-bold">0.00</p>
|
||||
</div>
|
||||
<div className="h-1 w-full bg-gray-900 overflow-hidden">
|
||||
<div className="h-full bg-gray-500 w-0" />
|
||||
@@ -276,8 +276,8 @@ export default function ProfileView({ onBack, persona, isCitizen, nodeId, public
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb-1">
|
||||
<p className="text-[10px] text-gray-500 uppercase tracking-widest">Temporal Burst Detection</p>
|
||||
<p className="text-[10px] text-gray-400 font-bold">0.00</p>
|
||||
<p className="text-sm text-gray-500 uppercase tracking-widest">Temporal Burst Detection</p>
|
||||
<p className="text-sm text-gray-400 font-bold">0.00</p>
|
||||
</div>
|
||||
<div className="h-1 w-full bg-gray-900 overflow-hidden">
|
||||
<div className="h-full bg-gray-500 w-0" />
|
||||
@@ -285,7 +285,7 @@ export default function ProfileView({ onBack, persona, isCitizen, nodeId, public
|
||||
</div>
|
||||
<div className="p-2 border border-gray-800 bg-gray-900/20 flex items-start gap-2">
|
||||
<AlertCircle size={14} className="text-gray-500 shrink-0 mt-0.5" />
|
||||
<p className="text-[9px] text-gray-500 uppercase leading-tight">
|
||||
<p className="text-[13px] text-gray-500 uppercase leading-tight">
|
||||
Advanced network-health analytics are not calibrated for this profile yet. Live reputation above is authoritative; unresolved analytics stay zeroed.
|
||||
</p>
|
||||
</div>
|
||||
@@ -299,27 +299,27 @@ export default function ProfileView({ onBack, persona, isCitizen, nodeId, public
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||
<div className="border border-gray-800 p-2 bg-[#0a0a0a]">
|
||||
<p className="text-[10px] text-gray-500 uppercase tracking-widest">Root</p>
|
||||
<p className="text-sm text-gray-500 uppercase tracking-widest">Root</p>
|
||||
<p className="text-xs text-red-400 font-bold">NEVER PUBLIC</p>
|
||||
</div>
|
||||
<div className="border border-gray-800 p-2 bg-[#0a0a0a]">
|
||||
<p className="text-[10px] text-gray-500 uppercase tracking-widest">Transport</p>
|
||||
<p className="text-sm text-gray-500 uppercase tracking-widest">Transport</p>
|
||||
<p className="text-xs text-green-400 font-bold">PUBLIC MESH</p>
|
||||
</div>
|
||||
<div className="border border-gray-800 p-2 bg-[#0a0a0a]">
|
||||
<p className="text-[10px] text-gray-500 uppercase tracking-widest">DM Alias</p>
|
||||
<p className="text-sm text-gray-500 uppercase tracking-widest">DM Alias</p>
|
||||
<p className="text-xs text-cyan-400 font-bold">SEMI-OBFUSCATED</p>
|
||||
</div>
|
||||
<div className="border border-gray-800 p-2 bg-[#0a0a0a]">
|
||||
<p className="text-[10px] text-gray-500 uppercase tracking-widest">Gate Session</p>
|
||||
<p className="text-sm text-gray-500 uppercase tracking-widest">Gate Session</p>
|
||||
<p className="text-xs text-cyan-400 font-bold">ANONYMOUS</p>
|
||||
</div>
|
||||
<div className="border border-gray-800 p-2 bg-[#0a0a0a]">
|
||||
<p className="text-[10px] text-gray-500 uppercase tracking-widest">Gate Persona</p>
|
||||
<p className="text-sm text-gray-500 uppercase tracking-widest">Gate Persona</p>
|
||||
<p className="text-xs text-cyan-400 font-bold">{displayPersona}</p>
|
||||
</div>
|
||||
<div className="border border-gray-800 p-2 bg-[#0a0a0a]">
|
||||
<p className="text-[10px] text-gray-500 uppercase tracking-widest">Credits</p>
|
||||
<p className="text-sm text-gray-500 uppercase tracking-widest">Credits</p>
|
||||
<p className="text-xs text-gray-300 font-bold">0.00 AVAILABLE</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -70,7 +70,7 @@ export default function TerminalDashboard({ onNavigate, onComingSoon }: Terminal
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-cyan-400 uppercase tracking-widest font-bold">GLOBAL THREAT INTERCEPT</span>
|
||||
{threat && (
|
||||
<span className={`text-[10px] px-2 py-0.5 ${threatStyle.bg} ${threatStyle.text} ${threatStyle.border} border animate-pulse font-bold`}>
|
||||
<span className={`text-sm px-2 py-0.5 ${threatStyle.bg} ${threatStyle.text} ${threatStyle.border} border animate-pulse font-bold`}>
|
||||
{threat.level}
|
||||
</span>
|
||||
)}
|
||||
@@ -101,16 +101,16 @@ export default function TerminalDashboard({ onNavigate, onComingSoon }: Terminal
|
||||
{filteredNews.length > 0 ? filteredNews.map((article, i) => (
|
||||
<div key={article.id || i} className="group cursor-pointer">
|
||||
<div className="flex items-baseline gap-2 mb-0.5">
|
||||
<span className={`text-[10px] uppercase tracking-widest border border-gray-800 px-1 ${
|
||||
<span className={`text-sm uppercase tracking-widest border border-gray-800 px-1 ${
|
||||
article.risk_score >= 7 ? 'text-red-400' :
|
||||
article.risk_score >= 4 ? 'text-yellow-400' : 'text-green-400'
|
||||
}`}>
|
||||
{article.risk_score >= 7 ? 'HIGH' : article.risk_score >= 4 ? 'MED' : 'LOW'}
|
||||
</span>
|
||||
<span className="text-[10px] text-gray-600 font-mono uppercase">{article.source}</span>
|
||||
<span className="text-[10px] text-gray-500 font-mono">{formatTime(article.pub_date)}</span>
|
||||
<span className="text-sm text-gray-600 font-mono uppercase">{article.source}</span>
|
||||
<span className="text-sm text-gray-500 font-mono">{formatTime(article.pub_date)}</span>
|
||||
{article.breaking && (
|
||||
<span className="text-[10px] text-red-500 font-bold animate-pulse">BREAKING</span>
|
||||
<span className="text-sm text-red-500 font-bold animate-pulse">BREAKING</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-gray-300 group-hover:text-white transition-colors leading-snug">{article.title}</p>
|
||||
@@ -204,28 +204,28 @@ export default function TerminalDashboard({ onNavigate, onComingSoon }: Terminal
|
||||
<div className="flex-1 border border-gray-800 bg-gray-900/20 p-3 flex flex-col justify-between">
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center border-b border-gray-800/50 pb-1">
|
||||
<span className="text-[10px] text-gray-500 uppercase tracking-widest">Tracked Flights</span>
|
||||
<span className="text-sm text-gray-500 uppercase tracking-widest">Tracked Flights</span>
|
||||
<span className="text-xs text-green-400 font-mono">{flightCount.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center border-b border-gray-800/50 pb-1">
|
||||
<span className="text-[10px] text-gray-500 uppercase tracking-widest">Tracked Vessels</span>
|
||||
<span className="text-sm text-gray-500 uppercase tracking-widest">Tracked Vessels</span>
|
||||
<span className="text-xs text-cyan-400 font-mono">{shipCount.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center border-b border-gray-800/50 pb-1">
|
||||
<span className="text-[10px] text-gray-500 uppercase tracking-widest">Satellites</span>
|
||||
<span className="text-sm text-gray-500 uppercase tracking-widest">Satellites</span>
|
||||
<span className="text-xs text-gray-300 font-mono">{satCount.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center border-b border-gray-800/50 pb-1">
|
||||
<span className="text-[10px] text-gray-500 uppercase tracking-widest">Active Markets</span>
|
||||
<span className="text-sm text-gray-500 uppercase tracking-widest">Active Markets</span>
|
||||
<span className="text-xs text-gray-300 font-mono">{markets.length}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center border-b border-gray-800/50 pb-1">
|
||||
<span className="text-[10px] text-gray-500 uppercase tracking-widest">Correlations</span>
|
||||
<span className="text-sm text-gray-500 uppercase tracking-widest">Correlations</span>
|
||||
<span className="text-xs text-amber-400 font-mono">{correlationCount}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-[10px] text-gray-500 uppercase tracking-widest">Threat Level</span>
|
||||
<span className={`text-[10px] px-2 py-0.5 ${threatStyle.bg} ${threatStyle.text} ${threatStyle.border} border ${threat?.level === 'SEVERE' || threat?.level === 'HIGH' ? 'animate-pulse' : ''}`}>
|
||||
<span className="text-sm text-gray-500 uppercase tracking-widest">Threat Level</span>
|
||||
<span className={`text-sm px-2 py-0.5 ${threatStyle.bg} ${threatStyle.text} ${threatStyle.border} border ${threat?.level === 'SEVERE' || threat?.level === 'HIGH' ? 'animate-pulse' : ''}`}>
|
||||
{threat?.level || 'UNKNOWN'} {threat?.score != null ? `(${threat.score})` : ''}
|
||||
</span>
|
||||
</div>
|
||||
@@ -234,9 +234,9 @@ export default function TerminalDashboard({ onNavigate, onComingSoon }: Terminal
|
||||
{/* Threat drivers */}
|
||||
{threat?.drivers && threat.drivers.length > 0 && (
|
||||
<div className="mt-3 pt-2 border-t border-gray-800">
|
||||
<span className="text-[8px] text-gray-500 uppercase tracking-widest block mb-1">THREAT DRIVERS</span>
|
||||
<span className="text-[12px] text-gray-500 uppercase tracking-widest block mb-1">THREAT DRIVERS</span>
|
||||
{threat.drivers.slice(0, 3).map((driver, i) => (
|
||||
<p key={i} className="text-[9px] text-gray-400 leading-tight">• {driver}</p>
|
||||
<p key={i} className="text-[13px] text-gray-400 leading-tight">• {driver}</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
@@ -247,8 +247,8 @@ export default function TerminalDashboard({ onNavigate, onComingSoon }: Terminal
|
||||
<div className="bg-green-500 flex-1"></div>
|
||||
</div>
|
||||
<div className="flex justify-between mt-1">
|
||||
<span className="text-[8px] text-gray-500 uppercase">Threat Score</span>
|
||||
<span className="text-[8px] text-gray-500 uppercase">{threat?.score ?? '—'}/100</span>
|
||||
<span className="text-[12px] text-gray-500 uppercase">Threat Score</span>
|
||||
<span className="text-[12px] text-gray-500 uppercase">{threat?.score ?? '—'}/100</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@ export default function TrendingPosts() {
|
||||
<MessageSquare size={14} className="mr-2" /> Gates
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
<div className="text-[10px] text-gray-500 leading-relaxed">
|
||||
<div className="text-sm text-gray-500 leading-relaxed">
|
||||
<p className="text-amber-400/80 font-bold mb-1">TEST-NET ACTIVE</p>
|
||||
<p>Gates are decentralized chatrooms running on the Infonet mesh. All messages are end-to-end encrypted via Wormhole.</p>
|
||||
<p className="mt-2">Type <span className="text-green-400 font-bold">gates</span> or <span className="text-green-400 font-bold">g/</span> to browse available rooms.</p>
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function WeatherWidget() {
|
||||
const dateString = time.toLocaleDateString('en-US', { timeZone: loc.tz, month: 'short', day: 'numeric' });
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2 text-[10px] md:text-xs text-gray-400 border border-gray-800 bg-gray-900/30 px-2 py-1 shrink-0 font-mono tracking-widest uppercase whitespace-nowrap">
|
||||
<div className="flex items-center gap-2 text-sm md:text-xs text-gray-400 border border-gray-800 bg-gray-900/30 px-2 py-1 shrink-0 font-mono tracking-widest uppercase whitespace-nowrap">
|
||||
<span>{dateString} {timeString}</span>
|
||||
<span className="text-gray-700">|</span>
|
||||
<span
|
||||
|
||||
@@ -44,7 +44,7 @@ export default function InfonetTerminal({ isOpen, onClose, onOpenLiveGate }: Inf
|
||||
<div className="flex items-center justify-between px-4 py-2 border-b border-gray-800/60 bg-[#080808] shrink-0 select-none">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-cyan-500/60 shadow-[0_0_6px_rgba(6,182,212,0.4)]" />
|
||||
<span className="text-[10px] tracking-[0.3em] text-gray-500 uppercase">
|
||||
<span className="text-sm tracking-[0.3em] text-gray-500 uppercase">
|
||||
Infonet Sovereign Shell v0.1.1
|
||||
</span>
|
||||
</div>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4709,7 +4709,7 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
className={`group flex w-full items-center justify-between gap-3 text-[12px] leading-[1.8] whitespace-pre-wrap break-all border border-fuchsia-500/15 bg-fuchsia-500/[0.03] pr-3 text-left font-mono transition-all hover:border-fuchsia-400/35 hover:bg-fuchsia-500/[0.08] ${lineColor(line.type)} ${lineChrome}`}
|
||||
>
|
||||
<span className="min-w-0 flex-1">{content}</span>
|
||||
<span className="shrink-0 border border-fuchsia-500/25 px-2 py-0.5 text-[9px] tracking-[0.18em] text-fuchsia-200 transition-colors group-hover:border-fuchsia-400/45 group-hover:text-fuchsia-100">
|
||||
<span className="shrink-0 border border-fuchsia-500/25 px-2 py-0.5 text-[13px] tracking-[0.18em] text-fuchsia-200 transition-colors group-hover:border-fuchsia-400/45 group-hover:text-fuchsia-100">
|
||||
{line.actionLabel || 'OPEN'}
|
||||
</span>
|
||||
</button>
|
||||
@@ -4748,9 +4748,9 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
onClick={() => runQuickCommand(String(command))}
|
||||
className={`${cardBase} ${tone}`}
|
||||
>
|
||||
<div className="text-[10px] tracking-[0.24em]">{title}</div>
|
||||
<div className="text-sm tracking-[0.24em]">{title}</div>
|
||||
<div className="mt-2 text-[11px] leading-6 text-slate-400">{desc}</div>
|
||||
<div className="mt-3 text-[8px] tracking-[0.16em] text-slate-500">
|
||||
<div className="mt-3 text-[12px] tracking-[0.16em] text-slate-500">
|
||||
{String(command)}
|
||||
</div>
|
||||
</button>
|
||||
@@ -4769,7 +4769,7 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
['PRIVATE DM INBOX', 'Check the experimental private dead drop', () => openSurface('inbox'), 'border-fuchsia-500/25 text-fuchsia-300'],
|
||||
].map(([title, desc, action, tone]) => (
|
||||
<button key={title as string} type="button" onClick={action as () => void} className={`${cardBase} ${tone}`}>
|
||||
<div className="text-[10px] tracking-[0.24em]">{title as string}</div>
|
||||
<div className="text-sm tracking-[0.24em]">{title as string}</div>
|
||||
<div className="mt-2 text-[11px] leading-6 text-slate-400">{desc as string}</div>
|
||||
</button>
|
||||
))}
|
||||
@@ -4793,8 +4793,8 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
className={`${cardBase} border-fuchsia-500/25 text-fuchsia-300 hover:border-fuchsia-400/45 hover:bg-fuchsia-500/10`}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="text-[10px] tracking-[0.24em]">{title}</div>
|
||||
<div className="text-[8px] tracking-[0.16em] text-amber-200">{command}</div>
|
||||
<div className="text-sm tracking-[0.24em]">{title}</div>
|
||||
<div className="text-[12px] tracking-[0.16em] text-amber-200">{command}</div>
|
||||
</div>
|
||||
<div className="mt-2 text-[11px] leading-6 text-slate-400">{desc}</div>
|
||||
</button>
|
||||
@@ -4806,22 +4806,22 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
<div className="space-y-3">
|
||||
<div className="grid gap-3 md:grid-cols-2">
|
||||
<div className="border border-emerald-500/20 bg-black/45 px-4 py-3 font-mono">
|
||||
<div className="text-[10px] tracking-[0.24em] text-emerald-300">PUBLIC MESH LANE</div>
|
||||
<div className="text-sm tracking-[0.24em] text-emerald-300">PUBLIC MESH LANE</div>
|
||||
<div className="mt-2 text-[11px] leading-6 text-slate-300">
|
||||
{publicAgentReady
|
||||
? `Public Agent active as ${nodeIdentity?.nodeId || 'unknown'}`
|
||||
: 'No public Agent yet. Type connect to create one for mesh posting.'}
|
||||
</div>
|
||||
<div className="mt-2 text-[10px] leading-5 text-emerald-200/75">
|
||||
<div className="mt-2 text-sm leading-5 text-emerald-200/75">
|
||||
Meshtastic traffic is public / observable. Wormhole is not required here.
|
||||
</div>
|
||||
</div>
|
||||
<div className="border border-cyan-500/20 bg-black/45 px-4 py-3 font-mono">
|
||||
<div className="text-[10px] tracking-[0.24em] text-cyan-300">WORMHOLE OBFUSCATED LANE</div>
|
||||
<div className="text-sm tracking-[0.24em] text-cyan-300">WORMHOLE OBFUSCATED LANE</div>
|
||||
<div className="mt-2 text-[11px] leading-6 text-slate-300">
|
||||
{privateLaneLabel}
|
||||
</div>
|
||||
<div className="mt-2 text-[10px] leading-5 text-cyan-200/75">
|
||||
<div className="mt-2 text-sm leading-5 text-cyan-200/75">
|
||||
{privateLaneDetail}
|
||||
</div>
|
||||
</div>
|
||||
@@ -4843,12 +4843,12 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
onClick={action}
|
||||
className={`${cardBase} border-emerald-500/25 text-emerald-300`}
|
||||
>
|
||||
<div className="text-[10px] tracking-[0.24em]">{title}</div>
|
||||
<div className="text-sm tracking-[0.24em]">{title}</div>
|
||||
<div className="mt-2 text-[11px] leading-6 text-slate-400">{desc}</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="text-[10px] tracking-[0.26em] text-emerald-300">MESH ROOT CARDS</div>
|
||||
<div className="text-sm tracking-[0.26em] text-emerald-300">MESH ROOT CARDS</div>
|
||||
{surfaceMeshLoading ? (
|
||||
<div className="border border-emerald-500/20 bg-black/45 px-4 py-5 text-[11px] font-mono text-slate-400">
|
||||
Loading mesh channels...
|
||||
@@ -4869,7 +4869,7 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-[11px] tracking-[0.22em] text-emerald-200">{region}</div>
|
||||
<div className="text-[10px] text-emerald-300">{count}</div>
|
||||
<div className="text-sm text-emerald-300">{count}</div>
|
||||
</div>
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
<button
|
||||
@@ -4878,14 +4878,14 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
setMeshRegion(region);
|
||||
runQuickCommand(`mesh listen 12`);
|
||||
}}
|
||||
className="border border-emerald-500/20 bg-emerald-500/8 px-3 py-1.5 text-[9px] tracking-[0.18em] text-emerald-300 hover:bg-emerald-500/14"
|
||||
className="border border-emerald-500/20 bg-emerald-500/8 px-3 py-1.5 text-[13px] tracking-[0.18em] text-emerald-300 hover:bg-emerald-500/14"
|
||||
>
|
||||
LISTEN
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setMeshRegion(region)}
|
||||
className="border border-cyan-500/20 bg-cyan-500/8 px-3 py-1.5 text-[9px] tracking-[0.18em] text-cyan-300 hover:bg-cyan-500/14"
|
||||
className="border border-cyan-500/20 bg-cyan-500/8 px-3 py-1.5 text-[13px] tracking-[0.18em] text-cyan-300 hover:bg-cyan-500/14"
|
||||
>
|
||||
SELECT
|
||||
</button>
|
||||
@@ -4911,12 +4911,12 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
onClick={action as () => void}
|
||||
className={`${cardBase} border-amber-400/25 text-amber-200`}
|
||||
>
|
||||
<div className="text-[10px] tracking-[0.24em]">{title as string}</div>
|
||||
<div className="text-sm tracking-[0.24em]">{title as string}</div>
|
||||
<div className="mt-2 text-[11px] leading-6 text-slate-400">{desc as string}</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="text-[10px] tracking-[0.26em] text-amber-200">LIVE MARKET CARDS</div>
|
||||
<div className="text-sm tracking-[0.26em] text-amber-200">LIVE MARKET CARDS</div>
|
||||
{surfaceMarketsLoading ? (
|
||||
<div className="border border-amber-400/20 bg-black/45 px-4 py-5 text-[11px] font-mono text-slate-400">
|
||||
Loading market cards...
|
||||
@@ -4940,39 +4940,39 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="text-[11px] leading-6 text-amber-100">{title}</div>
|
||||
<div className="border border-amber-400/20 bg-amber-400/8 px-2 py-1 text-[9px] text-amber-200">
|
||||
<div className="border border-amber-400/20 bg-amber-400/8 px-2 py-1 text-[13px] text-amber-200">
|
||||
{pctValue}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 text-[9px] tracking-[0.16em] text-slate-500">{category}</div>
|
||||
<div className="mt-2 text-[13px] tracking-[0.16em] text-slate-500">{category}</div>
|
||||
<div className="mt-4 flex flex-wrap gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setExpandedMarketIndex((prev) => (prev === idx ? null : idx))
|
||||
}
|
||||
className="border border-amber-400/20 bg-amber-400/8 px-3 py-1.5 text-[9px] tracking-[0.18em] text-amber-200 hover:bg-amber-400/14"
|
||||
className="border border-amber-400/20 bg-amber-400/8 px-3 py-1.5 text-[13px] tracking-[0.18em] text-amber-200 hover:bg-amber-400/14"
|
||||
>
|
||||
{expanded ? 'HIDE' : 'OPEN'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => runQuickCommand(`markets ${title}`)}
|
||||
className="border border-amber-400/20 bg-amber-400/8 px-3 py-1.5 text-[9px] tracking-[0.18em] text-amber-200 hover:bg-amber-400/14"
|
||||
className="border border-amber-400/20 bg-amber-400/8 px-3 py-1.5 text-[13px] tracking-[0.18em] text-amber-200 hover:bg-amber-400/14"
|
||||
>
|
||||
BOARD
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => runQuickCommand(`oracle ${nodeIdentity?.nodeId || ''}`.trim())}
|
||||
className="border border-cyan-500/20 bg-cyan-500/8 px-3 py-1.5 text-[9px] tracking-[0.18em] text-cyan-300 hover:bg-cyan-500/14"
|
||||
className="border border-cyan-500/20 bg-cyan-500/8 px-3 py-1.5 text-[13px] tracking-[0.18em] text-cyan-300 hover:bg-cyan-500/14"
|
||||
>
|
||||
PROFILE
|
||||
</button>
|
||||
</div>
|
||||
{expanded && (
|
||||
<div className="mt-4 border border-amber-400/15 bg-black/35 px-3 py-3 text-[10px] leading-6 text-slate-300">
|
||||
<div className="text-[9px] tracking-[0.18em] text-amber-200">MARKET DETAIL</div>
|
||||
<div className="mt-4 border border-amber-400/15 bg-black/35 px-3 py-3 text-sm leading-6 text-slate-300">
|
||||
<div className="text-[13px] tracking-[0.18em] text-amber-200">MARKET DETAIL</div>
|
||||
<div className="mt-2">Question: {title}</div>
|
||||
<div>Category: {category}</div>
|
||||
<div>Consensus: {pctValue}</div>
|
||||
@@ -5004,12 +5004,12 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
onClick={action as () => void}
|
||||
className={`${cardBase} border-cyan-500/25 text-cyan-300`}
|
||||
>
|
||||
<div className="text-[10px] tracking-[0.24em]">{title as string}</div>
|
||||
<div className="text-sm tracking-[0.24em]">{title as string}</div>
|
||||
<div className="mt-2 text-[11px] leading-6 text-slate-400">{desc as string}</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="text-[10px] tracking-[0.26em] text-cyan-300">EXPERIMENTAL PRIVATE DM INBOX</div>
|
||||
<div className="text-sm tracking-[0.26em] text-cyan-300">EXPERIMENTAL PRIVATE DM INBOX</div>
|
||||
{surfaceInboxLoading ? (
|
||||
<div className="border border-cyan-500/20 bg-black/45 px-4 py-5 text-[11px] font-mono text-slate-400">
|
||||
Checking inbox...
|
||||
@@ -5027,7 +5027,7 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="text-[11px] tracking-[0.18em] text-cyan-200">{message.sender}</div>
|
||||
<div className="text-[9px] text-slate-500">{message.age}</div>
|
||||
<div className="text-[13px] text-slate-500">{message.age}</div>
|
||||
</div>
|
||||
<div className="mt-3 text-[11px] leading-6 text-slate-300">
|
||||
{message.text}
|
||||
@@ -5036,14 +5036,14 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => runQuickCommand('inbox')}
|
||||
className="border border-cyan-500/20 bg-cyan-500/8 px-3 py-1.5 text-[9px] tracking-[0.18em] text-cyan-300 hover:bg-cyan-500/14"
|
||||
className="border border-cyan-500/20 bg-cyan-500/8 px-3 py-1.5 text-[13px] tracking-[0.18em] text-cyan-300 hover:bg-cyan-500/14"
|
||||
>
|
||||
OPEN
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => runQuickCommand(`dm ${message.sender}`)}
|
||||
className="border border-fuchsia-500/20 bg-fuchsia-500/8 px-3 py-1.5 text-[9px] tracking-[0.18em] text-fuchsia-300 hover:bg-fuchsia-500/14"
|
||||
className="border border-fuchsia-500/20 bg-fuchsia-500/8 px-3 py-1.5 text-[13px] tracking-[0.18em] text-fuchsia-300 hover:bg-fuchsia-500/14"
|
||||
>
|
||||
REPLY
|
||||
</button>
|
||||
@@ -5052,7 +5052,7 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="text-[10px] tracking-[0.26em] text-fuchsia-300">CONTACT CARDS</div>
|
||||
<div className="text-sm tracking-[0.26em] text-fuchsia-300">CONTACT CARDS</div>
|
||||
{contactEntries.length === 0 ? (
|
||||
<div className="border border-fuchsia-500/20 bg-black/45 px-4 py-5 text-[11px] font-mono text-slate-400">
|
||||
No saved contacts yet.
|
||||
@@ -5068,25 +5068,25 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
<div className="text-[11px] tracking-[0.18em] text-fuchsia-200">
|
||||
{contact.alias || contactId}
|
||||
</div>
|
||||
<div className="text-[9px] text-slate-500">
|
||||
<div className="text-[13px] text-slate-500">
|
||||
{contact.blocked ? 'BLOCKED' : 'ACTIVE'}
|
||||
</div>
|
||||
</div>
|
||||
{contact.alias && (
|
||||
<div className="mt-1 text-[9px] tracking-[0.14em] text-slate-500">{contactId}</div>
|
||||
<div className="mt-1 text-[13px] tracking-[0.14em] text-slate-500">{contactId}</div>
|
||||
)}
|
||||
<div className="mt-4 flex flex-wrap gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => runQuickCommand(`dm ${contactId}`)}
|
||||
className="border border-cyan-500/20 bg-cyan-500/8 px-3 py-1.5 text-[9px] tracking-[0.18em] text-cyan-300 hover:bg-cyan-500/14"
|
||||
className="border border-cyan-500/20 bg-cyan-500/8 px-3 py-1.5 text-[13px] tracking-[0.18em] text-cyan-300 hover:bg-cyan-500/14"
|
||||
>
|
||||
MESSAGE
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => runQuickCommand(contact.blocked ? `dm unblock ${contactId}` : `dm block ${contactId}`)}
|
||||
className="border border-fuchsia-500/20 bg-fuchsia-500/8 px-3 py-1.5 text-[9px] tracking-[0.18em] text-fuchsia-300 hover:bg-fuchsia-500/14"
|
||||
className="border border-fuchsia-500/20 bg-fuchsia-500/8 px-3 py-1.5 text-[13px] tracking-[0.18em] text-fuchsia-300 hover:bg-fuchsia-500/14"
|
||||
>
|
||||
{contact.blocked ? 'UNBLOCK' : 'BLOCK'}
|
||||
</button>
|
||||
@@ -5101,13 +5101,13 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-[10px] tracking-[0.28em] text-fuchsia-300">
|
||||
<div className="text-sm tracking-[0.28em] text-fuchsia-300">
|
||||
GATES (EXPERIMENTAL ENCRYPTION)
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => runQuickCommand('gates')}
|
||||
className="border border-fuchsia-500/25 bg-fuchsia-500/8 px-3 py-1.5 text-[9px] font-mono tracking-[0.22em] text-fuchsia-200 hover:bg-fuchsia-500/14"
|
||||
className="border border-fuchsia-500/25 bg-fuchsia-500/8 px-3 py-1.5 text-[13px] font-mono tracking-[0.22em] text-fuchsia-200 hover:bg-fuchsia-500/14"
|
||||
>
|
||||
OPEN GATE LOG
|
||||
</button>
|
||||
@@ -5135,18 +5135,18 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
<div className="text-[11px] tracking-[0.22em] text-fuchsia-200">
|
||||
{(gate.display_name || gate.gate_id).toUpperCase()}
|
||||
</div>
|
||||
<div className="mt-1 text-[9px] tracking-[0.16em] text-fuchsia-300/75">
|
||||
<div className="mt-1 text-[13px] tracking-[0.16em] text-fuchsia-300/75">
|
||||
{gate.gate_id}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-[9px] text-slate-500">
|
||||
<div className="text-[13px] text-slate-500">
|
||||
{typeof gate.message_count === 'number' ? `${gate.message_count} msgs` : 'catalog'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 min-h-[40px] text-[11px] leading-6 text-slate-400">
|
||||
{gate.description || 'Encrypted commons lane.'}
|
||||
</div>
|
||||
<div className="mt-3 flex items-center justify-between text-[9px] tracking-[0.16em]">
|
||||
<div className="mt-3 flex items-center justify-between text-[13px] tracking-[0.16em]">
|
||||
<span className="text-amber-200">{minRep ? `REQ ${minRep} REP` : 'OPEN'}</span>
|
||||
<span className="text-cyan-300">{gate.fixed ? 'FIXED LAUNCH GATE' : 'GATE'}</span>
|
||||
</div>
|
||||
@@ -5154,14 +5154,14 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openGateCard(gate.gate_id)}
|
||||
className="border border-cyan-500/25 bg-cyan-500/8 px-3 py-1.5 text-[9px] tracking-[0.18em] text-cyan-300 hover:bg-cyan-500/14"
|
||||
className="border border-cyan-500/25 bg-cyan-500/8 px-3 py-1.5 text-[13px] tracking-[0.18em] text-cyan-300 hover:bg-cyan-500/14"
|
||||
>
|
||||
{expanded ? 'HIDE' : 'OPEN'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => runQuickCommand(`messages ${gate.gate_id}`)}
|
||||
className="border border-emerald-500/25 bg-emerald-500/8 px-3 py-1.5 text-[9px] tracking-[0.18em] text-emerald-300 hover:bg-emerald-500/14"
|
||||
className="border border-emerald-500/25 bg-emerald-500/8 px-3 py-1.5 text-[13px] tracking-[0.18em] text-emerald-300 hover:bg-emerald-500/14"
|
||||
>
|
||||
MESSAGES
|
||||
</button>
|
||||
@@ -5173,20 +5173,20 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
setSurfacePanel('gates');
|
||||
setTimeout(() => inputRef.current?.focus(), 40);
|
||||
}}
|
||||
className="border border-amber-400/25 bg-amber-400/8 px-3 py-1.5 text-[9px] tracking-[0.18em] text-amber-200 hover:bg-amber-400/14"
|
||||
className="border border-amber-400/25 bg-amber-400/8 px-3 py-1.5 text-[13px] tracking-[0.18em] text-amber-200 hover:bg-amber-400/14"
|
||||
>
|
||||
POST
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => runQuickCommand(`gate mask ${gate.gate_id}`)}
|
||||
className="border border-fuchsia-500/25 bg-fuchsia-500/8 px-3 py-1.5 text-[9px] tracking-[0.18em] text-fuchsia-200 hover:bg-fuchsia-500/14"
|
||||
className="border border-fuchsia-500/25 bg-fuchsia-500/8 px-3 py-1.5 text-[13px] tracking-[0.18em] text-fuchsia-200 hover:bg-fuchsia-500/14"
|
||||
>
|
||||
UNLOCK
|
||||
</button>
|
||||
</div>
|
||||
{expandedGateLoading === gate.gate_id && (
|
||||
<div className="mt-4 border border-fuchsia-500/15 bg-black/35 px-3 py-3 text-[10px] text-slate-400">
|
||||
<div className="mt-4 border border-fuchsia-500/15 bg-black/35 px-3 py-3 text-sm text-slate-400">
|
||||
Loading gate detail...
|
||||
</div>
|
||||
)}
|
||||
@@ -5194,12 +5194,12 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
<div className="mt-4 space-y-3 border border-fuchsia-500/15 bg-black/40 px-4 py-4">
|
||||
<div className="grid gap-3 md:grid-cols-2">
|
||||
<div>
|
||||
<div className="text-[9px] tracking-[0.18em] text-fuchsia-300">WELCOME</div>
|
||||
<div className="text-[13px] tracking-[0.18em] text-fuchsia-300">WELCOME</div>
|
||||
<div className="mt-2 text-[11px] leading-6 text-slate-400">
|
||||
{expandedGateDetail.welcome || expandedGateDetail.description || 'Encrypted commons lane.'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2 text-[10px]">
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-slate-500">Creator</span>
|
||||
<span className="text-cyan-300">{expandedGateDetail.creator_node_id || 'unknown'}</span>
|
||||
@@ -5228,7 +5228,7 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
</div>
|
||||
{expandedGateMessages.length > 0 && (
|
||||
<div>
|
||||
<div className="text-[9px] tracking-[0.18em] text-fuchsia-300">THREAD SNAPSHOT</div>
|
||||
<div className="text-[13px] tracking-[0.18em] text-fuchsia-300">THREAD SNAPSHOT</div>
|
||||
<div className="mt-3 grid gap-3">
|
||||
{expandedGateMessages.map((message, messageIndex) => (
|
||||
<div
|
||||
@@ -5236,12 +5236,12 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
className="border border-cyan-500/15 bg-black/35 px-3 py-3 text-left transition-all hover:border-cyan-400/30 hover:bg-cyan-500/6"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="text-[10px] tracking-[0.16em] text-cyan-200">{message.nodeId}</div>
|
||||
<div className="text-[9px] text-slate-500">{message.age}</div>
|
||||
<div className="text-sm tracking-[0.16em] text-cyan-200">{message.nodeId}</div>
|
||||
<div className="text-[13px] text-slate-500">{message.age}</div>
|
||||
</div>
|
||||
<div className="mt-2 text-[11px] leading-6 text-slate-300">{message.text}</div>
|
||||
{message.encrypted && (
|
||||
<div className="mt-2 text-[9px] tracking-[0.16em] text-fuchsia-300">
|
||||
<div className="mt-2 text-[13px] tracking-[0.16em] text-fuchsia-300">
|
||||
EXPERIMENTAL ENCRYPTION
|
||||
</div>
|
||||
)}
|
||||
@@ -5249,7 +5249,7 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => runQuickCommand(`messages ${gate.gate_id}`)}
|
||||
className="border border-cyan-500/20 bg-cyan-500/8 px-3 py-1.5 text-[9px] tracking-[0.18em] text-cyan-300 hover:bg-cyan-500/14"
|
||||
className="border border-cyan-500/20 bg-cyan-500/8 px-3 py-1.5 text-[13px] tracking-[0.18em] text-cyan-300 hover:bg-cyan-500/14"
|
||||
>
|
||||
THREAD
|
||||
</button>
|
||||
@@ -5260,21 +5260,21 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
setGateReplyTarget(message.nodeId);
|
||||
setTimeout(() => inputRef.current?.focus(), 40);
|
||||
}}
|
||||
className="border border-amber-400/20 bg-amber-400/8 px-3 py-1.5 text-[9px] tracking-[0.18em] text-amber-200 hover:bg-amber-400/14"
|
||||
className="border border-amber-400/20 bg-amber-400/8 px-3 py-1.5 text-[13px] tracking-[0.18em] text-amber-200 hover:bg-amber-400/14"
|
||||
>
|
||||
REPLY
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => runQuickCommand(`rep ${message.nodeId}`)}
|
||||
className="border border-cyan-500/20 bg-cyan-500/8 px-3 py-1.5 text-[9px] tracking-[0.18em] text-cyan-300 hover:bg-cyan-500/14"
|
||||
className="border border-cyan-500/20 bg-cyan-500/8 px-3 py-1.5 text-[13px] tracking-[0.18em] text-cyan-300 hover:bg-cyan-500/14"
|
||||
>
|
||||
REP
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => runQuickCommand(`vote ${message.nodeId} up ${gate.gate_id}`)}
|
||||
className={`border px-3 py-1.5 text-[9px] tracking-[0.18em] transition-colors ${
|
||||
className={`border px-3 py-1.5 text-[13px] tracking-[0.18em] transition-colors ${
|
||||
voteDirections[voteScopeKey(message.nodeId, gate.gate_id)] === 1
|
||||
? 'border-emerald-400/35 bg-emerald-500/16 text-emerald-100'
|
||||
: 'border-emerald-500/20 bg-emerald-500/8 text-emerald-300 hover:bg-emerald-500/14'
|
||||
@@ -5285,7 +5285,7 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => runQuickCommand(`vote ${message.nodeId} down ${gate.gate_id}`)}
|
||||
className={`border px-3 py-1.5 text-[9px] tracking-[0.18em] transition-colors ${
|
||||
className={`border px-3 py-1.5 text-[13px] tracking-[0.18em] transition-colors ${
|
||||
voteDirections[voteScopeKey(message.nodeId, gate.gate_id)] === -1
|
||||
? 'border-rose-400/35 bg-rose-500/16 text-rose-100'
|
||||
: 'border-rose-500/20 bg-rose-500/8 text-rose-300 hover:bg-rose-500/14'
|
||||
@@ -5395,7 +5395,7 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
<div className="fixed inset-0 z-[310] bg-black/60 backdrop-blur-[2px]">
|
||||
<div className="pointer-events-none absolute inset-0 flex items-center justify-center p-4">
|
||||
<div className="pointer-events-auto w-full max-w-lg border border-cyan-500/25 bg-black/95 p-5 font-mono shadow-[0_0_42px_rgba(34,211,238,0.12)]">
|
||||
<div className="text-[10px] tracking-[0.28em] text-cyan-300">
|
||||
<div className="text-sm tracking-[0.28em] text-cyan-300">
|
||||
{privateLanePromptMode === 'enter' ? 'ENTER WORMHOLE' : 'ACTIVATE WORMHOLE'}
|
||||
</div>
|
||||
<div className="mt-3 text-[13px] leading-7 text-slate-200">
|
||||
@@ -5403,7 +5403,7 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
? 'Obfuscated lane detected. Enter Wormhole now to sync into the Infonet Commons and communicate through gates.'
|
||||
: 'No obfuscated lane is active yet. Activate Wormhole now and enter the Infonet Commons?'}
|
||||
</div>
|
||||
<div className="mt-4 border border-cyan-500/14 bg-cyan-950/10 px-4 py-3 text-[10px] leading-6 text-slate-300">
|
||||
<div className="mt-4 border border-cyan-500/14 bg-cyan-950/10 px-4 py-3 text-sm leading-6 text-slate-300">
|
||||
<div className="text-cyan-300">What this does</div>
|
||||
<div className="mt-2">Wormhole turns on the obfuscated lane for gates and the obfuscated commons.</div>
|
||||
<div>If a Wormhole identity already exists, it is reused. If one does not exist yet, it is bootstrapped once.</div>
|
||||
@@ -5412,7 +5412,7 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
</div>
|
||||
{privateLanePromptStatus && (
|
||||
<div
|
||||
className={`mt-4 border px-3 py-2 text-[10px] leading-6 ${
|
||||
className={`mt-4 border px-3 py-2 text-sm leading-6 ${
|
||||
privateLanePromptStatus.type === 'err'
|
||||
? 'border-rose-500/25 bg-rose-500/10 text-rose-200'
|
||||
: privateLanePromptStatus.type === 'ok'
|
||||
@@ -5428,7 +5428,7 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
type="button"
|
||||
onClick={confirmPrivateLanePrompt}
|
||||
disabled={privateLanePromptBusy}
|
||||
className="border border-cyan-500/25 bg-cyan-500/10 px-4 py-2 text-[10px] tracking-[0.22em] text-cyan-100 transition-colors hover:bg-cyan-500/16 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
className="border border-cyan-500/25 bg-cyan-500/10 px-4 py-2 text-sm tracking-[0.22em] text-cyan-100 transition-colors hover:bg-cyan-500/16 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{privateLanePromptBusy
|
||||
? 'ENTERING...'
|
||||
@@ -5440,7 +5440,7 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
type="button"
|
||||
onClick={dismissPrivateLanePrompt}
|
||||
disabled={privateLanePromptBusy}
|
||||
className="border border-slate-500/20 bg-white/5 px-4 py-2 text-[10px] tracking-[0.22em] text-slate-300 transition-colors hover:bg-white/8 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
className="border border-slate-500/20 bg-white/5 px-4 py-2 text-sm tracking-[0.22em] text-slate-300 transition-colors hover:bg-white/8 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
STAY PUBLIC
|
||||
</button>
|
||||
@@ -5459,7 +5459,7 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
onSettingsClick();
|
||||
}}
|
||||
disabled={privateLanePromptBusy}
|
||||
className="border border-slate-500/20 bg-white/5 px-4 py-2 text-[10px] tracking-[0.22em] text-slate-400 transition-colors hover:bg-white/8 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
className="border border-slate-500/20 bg-white/5 px-4 py-2 text-sm tracking-[0.22em] text-slate-400 transition-colors hover:bg-white/8 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
ADVANCED
|
||||
</button>
|
||||
@@ -5473,13 +5473,13 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
<div className="fixed inset-0 z-[309] bg-black/55 backdrop-blur-[2px]">
|
||||
<div className="pointer-events-none absolute inset-0 flex items-center justify-center p-4">
|
||||
<div className="pointer-events-auto w-full max-w-md border border-fuchsia-500/25 bg-black/95 p-5 font-mono shadow-[0_0_40px_rgba(217,70,239,0.12)]">
|
||||
<div className="text-[10px] tracking-[0.28em] text-fuchsia-300">
|
||||
<div className="text-sm tracking-[0.28em] text-fuchsia-300">
|
||||
ENTER INFONET COMMONS
|
||||
</div>
|
||||
<div className="mt-3 text-[12px] leading-6 text-slate-300">
|
||||
Gates live behind Wormhole in this build. Enter now?
|
||||
</div>
|
||||
<div className="mt-3 text-[10px] leading-5 text-slate-500">
|
||||
<div className="mt-3 text-sm leading-5 text-slate-500">
|
||||
{wormholeSecureRequired
|
||||
? wormholeReadyState
|
||||
? 'Yes takes you straight into the gates.'
|
||||
@@ -5490,14 +5490,14 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
<button
|
||||
type="button"
|
||||
onClick={confirmGateAccess}
|
||||
className="border border-fuchsia-500/25 bg-fuchsia-500/10 px-4 py-2 text-[10px] tracking-[0.22em] text-fuchsia-200 transition-colors hover:bg-fuchsia-500/16"
|
||||
className="border border-fuchsia-500/25 bg-fuchsia-500/10 px-4 py-2 text-sm tracking-[0.22em] text-fuchsia-200 transition-colors hover:bg-fuchsia-500/16"
|
||||
>
|
||||
YES
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={denyGateAccess}
|
||||
className="border border-slate-500/20 bg-white/5 px-4 py-2 text-[10px] tracking-[0.22em] text-slate-300 transition-colors hover:bg-white/8"
|
||||
className="border border-slate-500/20 bg-white/5 px-4 py-2 text-sm tracking-[0.22em] text-slate-300 transition-colors hover:bg-white/8"
|
||||
>
|
||||
NO
|
||||
</button>
|
||||
@@ -5520,7 +5520,7 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
className="fixed top-0 left-1/2 -translate-x-1/2 z-[305] flex items-center gap-2 rounded-b border border-cyan-800/30 border-t-0 bg-cyan-950/40 px-4 py-1.5 text-cyan-700 transition-colors hover:bg-cyan-950/60 hover:text-cyan-300 hover:border-cyan-500/40"
|
||||
>
|
||||
<Terminal size={11} className="text-cyan-400" />
|
||||
<span className="text-[7px] font-mono font-bold tracking-[0.22em]">
|
||||
<span className="text-[11px] font-mono font-bold tracking-[0.22em]">
|
||||
TERMINAL
|
||||
</span>
|
||||
</motion.button>
|
||||
@@ -5605,7 +5605,7 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<div className="text-[8px] tracking-[0.32em] text-slate-500">
|
||||
<div className="text-[12px] tracking-[0.32em] text-slate-500">
|
||||
type clear to wipe output · gates require wormhole · mesh stays public
|
||||
</div>
|
||||
</div>
|
||||
@@ -5617,22 +5617,22 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
openSurface('inbox');
|
||||
runQuickCommand('inbox');
|
||||
}}
|
||||
className="border border-cyan-500/18 bg-cyan-500/8 px-2.5 py-1 text-[8px] tracking-[0.18em] text-cyan-300 transition-colors hover:bg-cyan-500/14"
|
||||
className="border border-cyan-500/18 bg-cyan-500/8 px-2.5 py-1 text-[12px] tracking-[0.18em] text-cyan-300 transition-colors hover:bg-cyan-500/14"
|
||||
>
|
||||
PRIVATE DM INBOX
|
||||
</button>
|
||||
{nodeIdentity && hasSovereignty() && (
|
||||
<span className="border border-cyan-500/20 bg-cyan-500/10 px-2 py-1 text-[8px] tracking-[0.18em] text-cyan-300">
|
||||
<span className="border border-cyan-500/20 bg-cyan-500/10 px-2 py-1 text-[12px] tracking-[0.18em] text-cyan-300">
|
||||
{nodeIdentity.nodeId.slice(0, 14)}
|
||||
</span>
|
||||
)}
|
||||
{terminalWriteLockReason && (
|
||||
<span className="border border-amber-400/25 bg-amber-400/10 px-2 py-1 text-[8px] tracking-[0.18em] text-amber-200">
|
||||
<span className="border border-amber-400/25 bg-amber-400/10 px-2 py-1 text-[12px] tracking-[0.18em] text-amber-200">
|
||||
READ ONLY
|
||||
</span>
|
||||
)}
|
||||
{busy && (
|
||||
<span className="border border-fuchsia-500/25 bg-fuchsia-500/10 px-2 py-1 text-[8px] tracking-[0.18em] text-fuchsia-200">
|
||||
<span className="border border-fuchsia-500/25 bg-fuchsia-500/10 px-2 py-1 text-[12px] tracking-[0.18em] text-fuchsia-200">
|
||||
RUNNING
|
||||
</span>
|
||||
)}
|
||||
@@ -5671,11 +5671,11 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
<div className="absolute left-1/2 top-0 h-full w-px -translate-x-1/2 bg-cyan-400/20" />
|
||||
<div className="absolute top-1/2 left-0 h-px w-full -translate-y-1/2 bg-cyan-400/20" />
|
||||
</div>
|
||||
<div className="text-[10px] tracking-[0.38em] text-cyan-300">INFONET</div>
|
||||
<div className="text-sm tracking-[0.38em] text-cyan-300">INFONET</div>
|
||||
<div className="mt-2 text-[30px] font-semibold leading-none tracking-[0.32em] text-cyan-100">
|
||||
THE INFONET COMMONS
|
||||
</div>
|
||||
<div className="mt-2 text-[10px] tracking-[0.28em] text-fuchsia-300">
|
||||
<div className="mt-2 text-sm tracking-[0.28em] text-fuchsia-300">
|
||||
OPSINT DECK · COMMONS NODE
|
||||
</div>
|
||||
<div className="mt-4 max-w-[760px] text-[11px] leading-6 text-slate-400">
|
||||
@@ -5683,7 +5683,7 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-5 grid w-full gap-2 text-[9px] font-mono md:grid-cols-4">
|
||||
<div className="mt-5 grid w-full gap-2 text-[13px] font-mono md:grid-cols-4">
|
||||
<div className="border border-cyan-500/20 bg-cyan-500/8 px-3 py-2 text-cyan-300">
|
||||
INFONET · experimental encryption
|
||||
</div>
|
||||
@@ -5702,47 +5702,47 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
<div className="border border-cyan-500/16 bg-black/40 px-4 py-3">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<div className="text-[9px] tracking-[0.24em] text-cyan-300">
|
||||
<div className="text-[13px] tracking-[0.24em] text-cyan-300">
|
||||
PARTICIPANT NODE
|
||||
</div>
|
||||
<div className="mt-1 text-[10px] leading-5 text-slate-400">
|
||||
<div className="mt-1 text-sm leading-5 text-slate-400">
|
||||
Automatic bootstrap and sync now live on the backend lane. This node can keep a local chain even with Wormhole off.
|
||||
</div>
|
||||
</div>
|
||||
<div className="border border-cyan-500/20 bg-cyan-500/8 px-3 py-1.5 text-[9px] tracking-[0.22em] text-cyan-200">
|
||||
<div className="border border-cyan-500/20 bg-cyan-500/8 px-3 py-1.5 text-[13px] tracking-[0.22em] text-cyan-200">
|
||||
{nodeModeLabel}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 grid gap-2 md:grid-cols-3 text-[9px] font-mono">
|
||||
<div className="mt-3 grid gap-2 md:grid-cols-3 text-[13px] font-mono">
|
||||
<div className="border border-emerald-500/20 bg-emerald-500/8 px-3 py-2 text-emerald-200">
|
||||
<div className="text-[8px] tracking-[0.2em] text-emerald-300">CHAIN</div>
|
||||
<div className="text-[12px] tracking-[0.2em] text-emerald-300">CHAIN</div>
|
||||
<div className="mt-1 text-[13px] text-emerald-100">
|
||||
{shortNodeHash(infonetNodeStatus?.head_hash, 18)}
|
||||
</div>
|
||||
<div className="mt-1 text-[8px] text-emerald-200/70">
|
||||
<div className="mt-1 text-[12px] text-emerald-200/70">
|
||||
{Number(infonetNodeStatus?.total_events || 0)} events • {Number(infonetNodeStatus?.known_nodes || 0)} nodes
|
||||
</div>
|
||||
</div>
|
||||
<div className="border border-cyan-500/20 bg-cyan-500/8 px-3 py-2 text-cyan-200">
|
||||
<div className="text-[8px] tracking-[0.2em] text-cyan-300">PEERS</div>
|
||||
<div className="text-[12px] tracking-[0.2em] text-cyan-300">PEERS</div>
|
||||
<div className="mt-1 text-[13px] text-cyan-100">
|
||||
{Number(infonetNodeStatus?.bootstrap?.sync_peer_count || 0)} sync
|
||||
</div>
|
||||
<div className="mt-1 text-[8px] text-cyan-200/70">
|
||||
<div className="mt-1 text-[12px] text-cyan-200/70">
|
||||
{Number(infonetNodeStatus?.bootstrap?.push_peer_count || 0)} push • {Number(infonetNodeStatus?.bootstrap?.bootstrap_peer_count || 0)} bootstrap
|
||||
</div>
|
||||
</div>
|
||||
<div className="border border-fuchsia-500/20 bg-fuchsia-500/8 px-3 py-2 text-fuchsia-200">
|
||||
<div className="text-[8px] tracking-[0.2em] text-fuchsia-300">SYNC LOOP</div>
|
||||
<div className="text-[12px] tracking-[0.2em] text-fuchsia-300">SYNC LOOP</div>
|
||||
<div className="mt-1 text-[13px] text-fuchsia-100">{nodeSyncLabel}</div>
|
||||
<div className="mt-1 text-[8px] text-fuchsia-200/70">
|
||||
<div className="mt-1 text-[12px] text-fuchsia-200/70">
|
||||
next {formatNodeTime(infonetNodeStatus?.sync_runtime?.next_sync_due_at)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 border border-cyan-500/12 bg-cyan-950/8 px-3 py-2 text-[9px] font-mono leading-[1.65] text-slate-300">
|
||||
<div className="mt-3 border border-cyan-500/12 bg-cyan-950/8 px-3 py-2 text-[13px] font-mono leading-[1.65] text-slate-300">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<span className="text-cyan-300">Bootstrap</span>
|
||||
<span className="text-right text-slate-400">{nodeBootstrapLabel}</span>
|
||||
@@ -5759,8 +5759,8 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border border-amber-400/16 bg-amber-400/6 px-4 py-3 text-[10px] leading-6 text-amber-100/85">
|
||||
<div className="text-[9px] font-mono tracking-[0.24em] text-amber-300">
|
||||
<div className="border border-amber-400/16 bg-amber-400/6 px-4 py-3 text-sm leading-6 text-amber-100/85">
|
||||
<div className="text-[13px] font-mono tracking-[0.24em] text-amber-300">
|
||||
WORMHOLE OPTIONAL FOR NODE SYNC
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
@@ -5769,14 +5769,14 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
<div className="mt-2 text-amber-200/75">
|
||||
Turn Wormhole on for gates, obfuscated inbox, and the stronger obfuscated lane only.
|
||||
</div>
|
||||
<div className="mt-3 border border-amber-400/16 bg-black/20 px-3 py-2 text-[9px] font-mono leading-[1.65] text-amber-100/80">
|
||||
<div className="mt-3 border border-amber-400/16 bg-black/20 px-3 py-2 text-[13px] font-mono leading-[1.65] text-amber-100/80">
|
||||
obfuscated lane now: {privateLaneLabel}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void openPrivateLanePrompt()}
|
||||
disabled={busy || privateLanePromptBusy}
|
||||
className="mt-3 inline-flex items-center border border-amber-300/20 bg-amber-400/10 px-3 py-2 text-[9px] font-mono tracking-[0.22em] text-amber-100 transition-colors hover:bg-amber-400/16 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
className="mt-3 inline-flex items-center border border-amber-300/20 bg-amber-400/10 px-3 py-2 text-[13px] font-mono tracking-[0.22em] text-amber-100 transition-colors hover:bg-amber-400/16 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{wormholeSecureRequired && wormholeReadyState
|
||||
? 'ENTER WORMHOLE'
|
||||
@@ -5792,7 +5792,7 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
type="button"
|
||||
onClick={() => openSurface(item.panel)}
|
||||
disabled={busy}
|
||||
className={`px-3 py-2 text-[10px] font-mono tracking-[0.26em] transition-all disabled:cursor-not-allowed disabled:opacity-50 ${chipTone(item.tone)}`}
|
||||
className={`px-3 py-2 text-sm font-mono tracking-[0.26em] transition-all disabled:cursor-not-allowed disabled:opacity-50 ${chipTone(item.tone)}`}
|
||||
>
|
||||
{item.label}
|
||||
</button>
|
||||
@@ -5811,19 +5811,19 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
</div>
|
||||
|
||||
<div className="border-t border-cyan-500/15 bg-[linear-gradient(180deg,rgba(7,11,15,0.98),rgba(5,8,12,0.98))] px-4 py-3">
|
||||
<div className="mb-2 flex items-center justify-between text-[9px] font-mono tracking-[0.22em]">
|
||||
<div className="mb-2 flex items-center justify-between text-[13px] font-mono tracking-[0.22em]">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-cyan-300">COMMAND LINE</span>
|
||||
<span className="text-emerald-300">MESH / RADIO</span>
|
||||
<span className="text-fuchsia-300">GATES / COMMONS</span>
|
||||
<span className="text-amber-200">OPS / DOSSIER</span>
|
||||
{activeGateComposeId && (
|
||||
<span className="border border-fuchsia-500/20 bg-fuchsia-500/8 px-2 py-1 text-[8px] tracking-[0.16em] text-fuchsia-200">
|
||||
<span className="border border-fuchsia-500/20 bg-fuchsia-500/8 px-2 py-1 text-[12px] tracking-[0.16em] text-fuchsia-200">
|
||||
POSTING TO g/{activeGateComposeId}
|
||||
</span>
|
||||
)}
|
||||
{gateReplyTarget && (
|
||||
<span className="border border-amber-400/20 bg-amber-400/8 px-2 py-1 text-[8px] tracking-[0.16em] text-amber-200">
|
||||
<span className="border border-amber-400/20 bg-amber-400/8 px-2 py-1 text-[12px] tracking-[0.16em] text-amber-200">
|
||||
REPLY @{gateReplyTarget}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -107,7 +107,7 @@ const OnboardingModal = React.memo(function OnboardingModal({
|
||||
<h2 className="text-sm font-bold tracking-[0.2em] text-[var(--text-primary)] font-mono">
|
||||
MISSION BRIEFING
|
||||
</h2>
|
||||
<span className="text-[9px] text-[var(--text-muted)] font-mono tracking-widest">
|
||||
<span className="text-[13px] text-[var(--text-muted)] font-mono tracking-widest">
|
||||
FIRST-TIME SETUP
|
||||
</span>
|
||||
</div>
|
||||
@@ -127,7 +127,7 @@ const OnboardingModal = React.memo(function OnboardingModal({
|
||||
<button
|
||||
key={label}
|
||||
onClick={() => setStep(i)}
|
||||
className={`flex-1 py-1.5 text-[9px] font-mono tracking-widest border transition-all ${
|
||||
className={`flex-1 py-1.5 text-[13px] font-mono tracking-widest border transition-all ${
|
||||
step === i
|
||||
? 'border-cyan-500/50 text-cyan-400 bg-cyan-950/20'
|
||||
: 'border-[var(--border-primary)] text-[var(--text-muted)] hover:border-[var(--border-secondary)] hover:text-[var(--text-secondary)]'
|
||||
@@ -159,7 +159,7 @@ const OnboardingModal = React.memo(function OnboardingModal({
|
||||
<p className="text-[11px] text-yellow-400 font-mono font-bold mb-1">
|
||||
API Keys Required
|
||||
</p>
|
||||
<p className="text-[10px] text-[var(--text-secondary)] font-mono leading-relaxed">
|
||||
<p className="text-sm text-[var(--text-secondary)] font-mono leading-relaxed">
|
||||
Two API keys are needed for full functionality:{' '}
|
||||
<span className="text-cyan-400">OpenSky Network</span> (flights) and{' '}
|
||||
<span className="text-blue-400">AIS Stream</span> (ships). Both are free.
|
||||
@@ -176,7 +176,7 @@ const OnboardingModal = React.memo(function OnboardingModal({
|
||||
<p className="text-[11px] text-green-400 font-mono font-bold mb-1">
|
||||
8 Sources Work Immediately
|
||||
</p>
|
||||
<p className="text-[10px] text-[var(--text-secondary)] font-mono leading-relaxed">
|
||||
<p className="text-sm text-[var(--text-secondary)] font-mono leading-relaxed">
|
||||
Military aircraft, satellites, earthquakes, global conflicts, weather radar,
|
||||
radio scanners, news, and market data all work out of the box — no keys
|
||||
needed.
|
||||
@@ -192,7 +192,7 @@ const OnboardingModal = React.memo(function OnboardingModal({
|
||||
<p className="text-[11px] text-cyan-300 font-mono font-bold mb-1">
|
||||
TRUST MODES
|
||||
</p>
|
||||
<div className="space-y-1 text-[10px] text-[var(--text-secondary)] font-mono leading-relaxed">
|
||||
<div className="space-y-1 text-sm text-[var(--text-secondary)] font-mono leading-relaxed">
|
||||
<div>
|
||||
<span className="text-orange-300">PUBLIC / DEGRADED</span> — Meshtastic,
|
||||
APRS, and perimeter feeds. Observable and linkable.
|
||||
@@ -206,7 +206,7 @@ const OnboardingModal = React.memo(function OnboardingModal({
|
||||
Reticulum are both ready.
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-2 text-[10px] text-[var(--text-secondary)] font-mono leading-relaxed">
|
||||
<p className="mt-2 text-sm text-[var(--text-secondary)] font-mono leading-relaxed">
|
||||
Public mesh is not private just because Wormhole exists. Use Wormhole when
|
||||
you want the private lane, and treat public mesh as public.
|
||||
</p>
|
||||
@@ -227,7 +227,7 @@ const OnboardingModal = React.memo(function OnboardingModal({
|
||||
<div className="flex items-center gap-2">
|
||||
{api.icon}
|
||||
<span className="text-xs font-mono text-white font-bold">{api.name}</span>
|
||||
<span className="text-[8px] font-mono px-1.5 py-0.5 border border-yellow-500/30 text-yellow-400 bg-yellow-950/20">
|
||||
<span className="text-[12px] font-mono px-1.5 py-0.5 border border-yellow-500/30 text-yellow-400 bg-yellow-950/20">
|
||||
REQUIRED
|
||||
</span>
|
||||
</div>
|
||||
@@ -235,23 +235,23 @@ const OnboardingModal = React.memo(function OnboardingModal({
|
||||
href={api.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={`text-[10px] font-mono text-${api.color}-400 hover:text-${api.color}-300 flex items-center gap-1 transition-colors`}
|
||||
className={`text-sm font-mono text-${api.color}-400 hover:text-${api.color}-300 flex items-center gap-1 transition-colors`}
|
||||
>
|
||||
GET KEY <ExternalLink size={10} />
|
||||
</a>
|
||||
</div>
|
||||
<p className="text-[10px] text-[var(--text-secondary)] font-mono mb-3">
|
||||
<p className="text-sm text-[var(--text-secondary)] font-mono mb-3">
|
||||
{api.description}
|
||||
</p>
|
||||
<ol className="space-y-1.5">
|
||||
{api.steps.map((s, i) => (
|
||||
<li key={i} className="flex items-start gap-2">
|
||||
<span
|
||||
className={`text-[9px] font-mono text-${api.color}-500 font-bold mt-0.5 w-3 flex-shrink-0`}
|
||||
className={`text-[13px] font-mono text-${api.color}-500 font-bold mt-0.5 w-3 flex-shrink-0`}
|
||||
>
|
||||
{i + 1}.
|
||||
</span>
|
||||
<span className="text-[10px] text-gray-300 font-mono">{s}</span>
|
||||
<span className="text-sm text-gray-300 font-mono">{s}</span>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
@@ -270,7 +270,7 @@ const OnboardingModal = React.memo(function OnboardingModal({
|
||||
|
||||
{step === 2 && (
|
||||
<div className="space-y-3">
|
||||
<p className="text-[10px] text-[var(--text-secondary)] font-mono mb-3">
|
||||
<p className="text-sm text-[var(--text-secondary)] font-mono mb-3">
|
||||
These data sources are completely free and require no API keys. They activate
|
||||
automatically on launch.
|
||||
</p>
|
||||
@@ -282,11 +282,11 @@ const OnboardingModal = React.memo(function OnboardingModal({
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-green-500">{src.icon}</span>
|
||||
<span className="text-[10px] font-mono text-[var(--text-primary)] font-medium">
|
||||
<span className="text-sm font-mono text-[var(--text-primary)] font-medium">
|
||||
{src.name}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-[9px] text-[var(--text-muted)] font-mono">{src.desc}</p>
|
||||
<p className="text-[13px] text-[var(--text-muted)] font-mono">{src.desc}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -298,7 +298,7 @@ const OnboardingModal = React.memo(function OnboardingModal({
|
||||
<div className="p-4 border-t border-[var(--border-primary)]/80 flex items-center justify-between">
|
||||
<button
|
||||
onClick={() => setStep(Math.max(0, step - 1))}
|
||||
className={`px-4 py-2 border text-[10px] font-mono tracking-widest transition-all ${
|
||||
className={`px-4 py-2 border text-sm font-mono tracking-widest transition-all ${
|
||||
step === 0
|
||||
? 'border-[var(--border-primary)] text-[var(--text-muted)] cursor-not-allowed'
|
||||
: 'border-[var(--border-primary)] text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:border-[var(--border-secondary)]'
|
||||
@@ -320,14 +320,14 @@ const OnboardingModal = React.memo(function OnboardingModal({
|
||||
{step < 2 ? (
|
||||
<button
|
||||
onClick={() => setStep(step + 1)}
|
||||
className="px-4 py-2 border border-cyan-500/40 text-cyan-400 hover:bg-cyan-500/10 text-[10px] font-mono tracking-widest transition-all"
|
||||
className="px-4 py-2 border border-cyan-500/40 text-cyan-400 hover:bg-cyan-500/10 text-sm font-mono tracking-widest transition-all"
|
||||
>
|
||||
NEXT
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={handleDismiss}
|
||||
className="px-4 py-2 bg-cyan-500/20 border border-cyan-500/40 text-cyan-400 hover:bg-cyan-500/30 text-[10px] font-mono tracking-widest transition-all"
|
||||
className="px-4 py-2 bg-cyan-500/20 border border-cyan-500/40 text-cyan-400 hover:bg-cyan-500/30 text-sm font-mono tracking-widest transition-all"
|
||||
>
|
||||
LAUNCH
|
||||
</button>
|
||||
|
||||
@@ -829,7 +829,7 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
<h2 className="text-sm font-bold tracking-[0.2em] text-[var(--text-primary)] font-mono">
|
||||
SYSTEM CONFIG
|
||||
</h2>
|
||||
<span className="text-[9px] text-[var(--text-muted)] font-mono tracking-widest">
|
||||
<span className="text-[13px] text-[var(--text-muted)] font-mono tracking-widest">
|
||||
SETTINGS & DATA SOURCES
|
||||
</span>
|
||||
</div>
|
||||
@@ -848,15 +848,15 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<Shield size={12} className="text-cyan-400" />
|
||||
<div className="min-w-0">
|
||||
<div className="text-[9px] font-mono tracking-widest text-cyan-300">WORMHOLE FIRST-RUN</div>
|
||||
<div className="text-[8px] font-mono text-[var(--text-muted)] mt-0.5">
|
||||
<div className="text-[13px] font-mono tracking-widest text-cyan-300">WORMHOLE FIRST-RUN</div>
|
||||
<div className="text-[12px] font-mono text-[var(--text-muted)] mt-0.5">
|
||||
Wormhole join below does not need operator tools. API/news tabs do.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowOperatorTools(true)}
|
||||
className="px-2 py-1 border border-cyan-500/30 text-[8px] font-mono text-cyan-300/80 tracking-widest hover:text-cyan-200 hover:border-cyan-400/40"
|
||||
className="px-2 py-1 border border-cyan-500/30 text-[12px] font-mono text-cyan-300/80 tracking-widest hover:text-cyan-200 hover:border-cyan-400/40"
|
||||
>
|
||||
OPERATOR TOOLS
|
||||
</button>
|
||||
@@ -868,7 +868,7 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
size={12}
|
||||
className={adminSessionReady ? 'text-green-400' : 'text-yellow-500'}
|
||||
/>
|
||||
<span className="text-[9px] font-mono tracking-widest text-[var(--text-muted)] whitespace-nowrap">
|
||||
<span className="text-[13px] font-mono tracking-widest text-[var(--text-muted)] whitespace-nowrap">
|
||||
OPERATOR TOOLS
|
||||
</span>
|
||||
<input
|
||||
@@ -885,13 +885,13 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
? 'Operator tools unlocked. Enter key only to reseed or recover...'
|
||||
: 'Enter operator key for protected settings tabs...'
|
||||
}
|
||||
className="flex-1 bg-[var(--bg-primary)]/60 border border-[var(--border-primary)] px-2 py-1 text-[10px] font-mono text-[var(--text-secondary)] outline-none focus:border-cyan-700 placeholder:text-[var(--text-muted)]/50"
|
||||
className="flex-1 bg-[var(--bg-primary)]/60 border border-[var(--border-primary)] px-2 py-1 text-sm font-mono text-[var(--text-secondary)] outline-none focus:border-cyan-700 placeholder:text-[var(--text-muted)]/50"
|
||||
/>
|
||||
{adminSessionReady ? (
|
||||
<button
|
||||
onClick={() => void lockAdminSession()}
|
||||
disabled={adminSessionBusy}
|
||||
className="px-2 py-1 border border-red-500/30 text-[8px] font-mono text-red-300/80 tracking-widest hover:text-red-200 hover:border-red-400/40 disabled:opacity-50"
|
||||
className="px-2 py-1 border border-red-500/30 text-[12px] font-mono text-red-300/80 tracking-widest hover:text-red-200 hover:border-red-400/40 disabled:opacity-50"
|
||||
>
|
||||
LOCK
|
||||
</button>
|
||||
@@ -899,7 +899,7 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
<button
|
||||
onClick={() => void unlockAdminSession()}
|
||||
disabled={adminSessionBusy || !adminKey.trim()}
|
||||
className="px-2 py-1 border border-cyan-500/30 text-[8px] font-mono text-cyan-300/80 tracking-widest hover:text-cyan-200 hover:border-cyan-400/40 disabled:opacity-50"
|
||||
className="px-2 py-1 border border-cyan-500/30 text-[12px] font-mono text-cyan-300/80 tracking-widest hover:text-cyan-200 hover:border-cyan-400/40 disabled:opacity-50"
|
||||
>
|
||||
UNLOCK
|
||||
</button>
|
||||
@@ -907,13 +907,13 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
{activeTab === 'protocol' && (
|
||||
<button
|
||||
onClick={() => setShowOperatorTools(false)}
|
||||
className="px-2 py-1 border border-[var(--border-primary)] text-[8px] font-mono text-[var(--text-muted)] tracking-widest hover:text-cyan-300 hover:border-cyan-500/40"
|
||||
className="px-2 py-1 border border-[var(--border-primary)] text-[12px] font-mono text-[var(--text-muted)] tracking-widest hover:text-cyan-300 hover:border-cyan-500/40"
|
||||
>
|
||||
HIDE
|
||||
</button>
|
||||
)}
|
||||
<span
|
||||
className={`text-[8px] font-mono tracking-widest ${
|
||||
className={`text-[12px] font-mono tracking-widest ${
|
||||
adminSessionReady ? 'text-green-400/70' : 'text-yellow-400/70'
|
||||
}`}
|
||||
>
|
||||
@@ -923,7 +923,7 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
{adminSessionMsg && (
|
||||
<div className="px-4 py-1.5 border-b border-[var(--border-primary)]/20 bg-[var(--bg-primary)]/20">
|
||||
<span
|
||||
className={`text-[8px] font-mono tracking-widest ${
|
||||
className={`text-[12px] font-mono tracking-widest ${
|
||||
adminSessionReady ? 'text-green-300/80' : 'text-yellow-300/80'
|
||||
}`}
|
||||
>
|
||||
@@ -934,7 +934,7 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
</>
|
||||
)}
|
||||
{adminSessionMsg === 'BACKEND ADMIN KEY NOT CONFIGURED' && activeTab !== 'protocol' && (
|
||||
<div className="mx-4 mt-3 border border-yellow-500/25 bg-yellow-950/10 px-3 py-3 text-[10px] font-mono text-yellow-200/90 leading-relaxed">
|
||||
<div className="mx-4 mt-3 border border-yellow-500/25 bg-yellow-950/10 px-3 py-3 text-sm font-mono text-yellow-200/90 leading-relaxed">
|
||||
<div>
|
||||
This is not an old market/API key problem. The backend admin secret itself is
|
||||
not configured, so protected Settings tabs cannot load.
|
||||
@@ -945,18 +945,18 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
const el = document.querySelector<HTMLInputElement>('input[type="password"]');
|
||||
el?.focus();
|
||||
}}
|
||||
className="px-3 py-1.5 border border-yellow-400/40 bg-yellow-950/20 text-[9px] font-mono tracking-[0.18em] text-yellow-200 hover:bg-yellow-950/30"
|
||||
className="px-3 py-1.5 border border-yellow-400/40 bg-yellow-950/20 text-[13px] font-mono tracking-[0.18em] text-yellow-200 hover:bg-yellow-950/30"
|
||||
>
|
||||
PASTE ADMIN KEY
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('protocol')}
|
||||
className="px-3 py-1.5 border border-cyan-500/35 bg-cyan-950/18 text-[9px] font-mono tracking-[0.18em] text-cyan-200 hover:bg-cyan-950/28"
|
||||
className="px-3 py-1.5 border border-cyan-500/35 bg-cyan-950/18 text-[13px] font-mono tracking-[0.18em] text-cyan-200 hover:bg-cyan-950/28"
|
||||
>
|
||||
BACK TO WORMHOLE
|
||||
</button>
|
||||
</div>
|
||||
<div className="mt-3 text-[9px] text-yellow-100/70">
|
||||
<div className="mt-3 text-[13px] text-yellow-100/70">
|
||||
Add <span className="text-cyan-300">ADMIN_KEY</span> to{' '}
|
||||
<span className="text-cyan-300">backend/.env</span>, restart the backend, then
|
||||
paste that same key above and unlock.
|
||||
@@ -967,14 +967,14 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
<div className="flex border-b border-[var(--border-primary)]/60">
|
||||
<button
|
||||
onClick={() => setActiveTab('api-keys')}
|
||||
className={`flex-1 px-4 py-2.5 text-[10px] font-mono tracking-widest font-bold transition-colors flex items-center justify-center gap-1.5 ${activeTab === 'api-keys' ? 'text-cyan-400 border-b-2 border-cyan-500 bg-cyan-950/10' : 'text-[var(--text-muted)] hover:text-[var(--text-secondary)]'}`}
|
||||
className={`flex-1 px-4 py-2.5 text-sm font-mono tracking-widest font-bold transition-colors flex items-center justify-center gap-1.5 ${activeTab === 'api-keys' ? 'text-cyan-400 border-b-2 border-cyan-500 bg-cyan-950/10' : 'text-[var(--text-muted)] hover:text-[var(--text-secondary)]'}`}
|
||||
>
|
||||
<Key size={10} />
|
||||
API KEYS
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('news-feeds')}
|
||||
className={`flex-1 px-4 py-2.5 text-[10px] font-mono tracking-widest font-bold transition-colors flex items-center justify-center gap-1.5 ${activeTab === 'news-feeds' ? 'text-orange-400 border-b-2 border-orange-500 bg-orange-950/10' : 'text-[var(--text-muted)] hover:text-[var(--text-secondary)]'}`}
|
||||
className={`flex-1 px-4 py-2.5 text-sm font-mono tracking-widest font-bold transition-colors flex items-center justify-center gap-1.5 ${activeTab === 'news-feeds' ? 'text-orange-400 border-b-2 border-orange-500 bg-orange-950/10' : 'text-[var(--text-muted)] hover:text-[var(--text-secondary)]'}`}
|
||||
>
|
||||
<Rss size={10} />
|
||||
NEWS FEEDS
|
||||
@@ -984,14 +984,14 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('sentinel')}
|
||||
className={`flex-1 px-4 py-2.5 text-[10px] font-mono tracking-widest font-bold transition-colors flex items-center justify-center gap-1.5 ${activeTab === 'sentinel' ? 'text-purple-400 border-b-2 border-purple-500 bg-purple-950/10' : 'text-[var(--text-muted)] hover:text-[var(--text-secondary)]'}`}
|
||||
className={`flex-1 px-4 py-2.5 text-sm font-mono tracking-widest font-bold transition-colors flex items-center justify-center gap-1.5 ${activeTab === 'sentinel' ? 'text-purple-400 border-b-2 border-purple-500 bg-purple-950/10' : 'text-[var(--text-muted)] hover:text-[var(--text-secondary)]'}`}
|
||||
>
|
||||
<Satellite size={10} />
|
||||
SENTINEL
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('protocol')}
|
||||
className={`flex-1 px-4 py-2.5 text-[10px] font-mono tracking-widest font-bold transition-colors flex items-center justify-center gap-1.5 ${activeTab === 'protocol' ? 'text-green-400 border-b-2 border-green-500 bg-green-950/10' : 'text-[var(--text-muted)] hover:text-[var(--text-secondary)]'}`}
|
||||
className={`flex-1 px-4 py-2.5 text-sm font-mono tracking-widest font-bold transition-colors flex items-center justify-center gap-1.5 ${activeTab === 'protocol' ? 'text-green-400 border-b-2 border-green-500 bg-green-950/10' : 'text-[var(--text-muted)] hover:text-[var(--text-secondary)]'}`}
|
||||
>
|
||||
<Shield size={10} />
|
||||
MESH
|
||||
@@ -1005,16 +1005,16 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
<div className="mx-4 mt-4 p-3 border border-cyan-900/30 bg-cyan-950/12">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<div className="text-[10px] text-cyan-300 font-mono tracking-[0.18em]">
|
||||
<div className="text-sm text-cyan-300 font-mono tracking-[0.18em]">
|
||||
WORMHOLE KEY SETUP
|
||||
</div>
|
||||
<div className="mt-2 text-[10px] text-[var(--text-secondary)] font-mono leading-relaxed">
|
||||
<div className="mt-2 text-sm text-[var(--text-secondary)] font-mono leading-relaxed">
|
||||
One click enters Wormhole on the recommended path for gates and the obfuscated
|
||||
inbox. Manual transport tuning stays hidden unless you ask for it.
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-[8px] text-[var(--text-muted)] font-mono tracking-[0.2em]">
|
||||
<div className="text-[12px] text-[var(--text-muted)] font-mono tracking-[0.2em]">
|
||||
STATUS
|
||||
</div>
|
||||
<div className="mt-1 text-[11px] font-mono text-cyan-200">
|
||||
@@ -1026,19 +1026,19 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 grid gap-2 text-[9px] font-mono text-[var(--text-muted)] leading-relaxed">
|
||||
<div className="mt-3 grid gap-2 text-[13px] font-mono text-[var(--text-muted)] leading-relaxed">
|
||||
<div>1. Press <span className="text-green-300">GET WORMHOLE KEY</span>.</div>
|
||||
<div>2. We handle the recommended setup path in the background.</div>
|
||||
<div>3. Wait for <span className="text-green-300">ACTIVE</span>.</div>
|
||||
<div>4. We send you straight back into gates.</div>
|
||||
</div>
|
||||
{wormholeGuideNotice && (
|
||||
<div className="mt-3 border border-fuchsia-500/25 bg-fuchsia-950/12 px-3 py-2 text-[10px] font-mono text-fuchsia-200/90 leading-relaxed">
|
||||
<div className="mt-3 border border-fuchsia-500/25 bg-fuchsia-950/12 px-3 py-2 text-sm font-mono text-fuchsia-200/90 leading-relaxed">
|
||||
{wormholeGuideNotice}
|
||||
</div>
|
||||
)}
|
||||
{adminSessionMsg === 'BACKEND ADMIN KEY NOT CONFIGURED' && (
|
||||
<div className="mt-3 border border-cyan-500/20 bg-cyan-950/10 px-3 py-2 text-[10px] font-mono text-cyan-200/85 leading-relaxed">
|
||||
<div className="mt-3 border border-cyan-500/20 bg-cyan-950/10 px-3 py-2 text-sm font-mono text-cyan-200/85 leading-relaxed">
|
||||
Operator key is only needed for protected Settings tabs. Wormhole join below now
|
||||
works without it.
|
||||
</div>
|
||||
@@ -1047,27 +1047,27 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
<button
|
||||
onClick={quickStartWormhole}
|
||||
disabled={wormholeSaving || wormholeQuickState === 'active'}
|
||||
className="px-3 py-1.5 border border-green-500/40 bg-green-950/20 text-[9px] font-mono tracking-[0.18em] text-green-300 hover:bg-green-950/30 disabled:opacity-40"
|
||||
className="px-3 py-1.5 border border-green-500/40 bg-green-950/20 text-[13px] font-mono tracking-[0.18em] text-green-300 hover:bg-green-950/30 disabled:opacity-40"
|
||||
>
|
||||
{wormholeQuickButtonLabel}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowAdvancedWormhole((prev) => !prev)}
|
||||
className="px-3 py-1.5 border border-cyan-500/35 bg-cyan-950/18 text-[9px] font-mono tracking-[0.18em] text-cyan-200 hover:bg-cyan-950/28"
|
||||
className="px-3 py-1.5 border border-cyan-500/35 bg-cyan-950/18 text-[13px] font-mono tracking-[0.18em] text-cyan-200 hover:bg-cyan-950/28"
|
||||
>
|
||||
{showAdvancedWormhole ? 'HIDE MANUAL SETUP' : 'MANUAL SETUP'}
|
||||
</button>
|
||||
</div>
|
||||
{wormholeMsg && (
|
||||
<div
|
||||
className={`mt-3 px-3 py-2 text-[10px] font-mono leading-relaxed ${wormholeMsg.type === 'ok' ? 'text-green-300 bg-green-950/18 border border-green-900/30' : 'text-red-300 bg-red-950/18 border border-red-900/30'}`}
|
||||
className={`mt-3 px-3 py-2 text-sm font-mono leading-relaxed ${wormholeMsg.type === 'ok' ? 'text-green-300 bg-green-950/18 border border-green-900/30' : 'text-red-300 bg-red-950/18 border border-red-900/30'}`}
|
||||
>
|
||||
{wormholeMsg.text}
|
||||
</div>
|
||||
)}
|
||||
{wormholeNodeId && (
|
||||
<div className="mt-3 border border-cyan-500/20 bg-black/30 px-3 py-2">
|
||||
<div className="text-[9px] font-mono tracking-[0.18em] text-[var(--text-muted)] mb-1">
|
||||
<div className="text-[13px] font-mono tracking-[0.18em] text-[var(--text-muted)] mb-1">
|
||||
YOUR WORMHOLE IDENTITY
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -1082,7 +1082,7 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
setTimeout(() => setWormholeKeyCopied(false), 2000);
|
||||
} catch { /* clipboard not available */ }
|
||||
}}
|
||||
className="shrink-0 px-2 py-1 border border-cyan-500/30 text-cyan-400 hover:bg-cyan-950/30 transition-colors text-[9px] font-mono flex items-center gap-1"
|
||||
className="shrink-0 px-2 py-1 border border-cyan-500/30 text-cyan-400 hover:bg-cyan-950/30 transition-colors text-[13px] font-mono flex items-center gap-1"
|
||||
title="Copy identity to clipboard"
|
||||
>
|
||||
{wormholeKeyCopied ? <Check size={10} /> : <Copy size={10} />}
|
||||
@@ -1100,7 +1100,7 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Shield size={12} className="text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span className="text-[10px] text-[var(--text-secondary)] font-mono tracking-widest">
|
||||
<span className="text-sm text-[var(--text-secondary)] font-mono tracking-widest">
|
||||
HIGH PRIVACY MODE (OPT-IN)
|
||||
</span>
|
||||
</div>
|
||||
@@ -1109,19 +1109,19 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
const next = privacyProfile !== 'high';
|
||||
setHighPrivacy(next);
|
||||
}}
|
||||
className={`px-2 py-1 border text-[9px] font-mono tracking-widest transition-colors ${privacyProfile === 'high' ? 'border-green-500/40 text-green-400 bg-green-950/20' : 'border-[var(--border-primary)] text-[var(--text-muted)] hover:text-[var(--text-secondary)]'}`}
|
||||
className={`px-2 py-1 border text-[13px] font-mono tracking-widest transition-colors ${privacyProfile === 'high' ? 'border-green-500/40 text-green-400 bg-green-950/20' : 'border-[var(--border-primary)] text-[var(--text-muted)] hover:text-[var(--text-secondary)]'}`}
|
||||
>
|
||||
{privacyProfile === 'high' ? 'ON' : 'OFF'}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-[10px] text-[var(--text-muted)] font-mono leading-relaxed mt-2">
|
||||
<p className="text-sm text-[var(--text-muted)] font-mono leading-relaxed mt-2">
|
||||
Enables High Privacy profile: session-only identity, stronger jitter, sharded
|
||||
transport (when available), and stricter sync behavior. High Privacy requires
|
||||
the local agent for mesh traffic and refuses clearnet fallback for obfuscated
|
||||
sends. This does not make you anonymous or fully hidden.
|
||||
</p>
|
||||
{privacyProfile === 'high' && (
|
||||
<div className="mt-2 p-2 border border-yellow-500/30 bg-yellow-950/10 text-[10px] text-yellow-200/90 font-mono leading-relaxed">
|
||||
<div className="mt-2 p-2 border border-yellow-500/30 bg-yellow-950/10 text-sm text-yellow-200/90 font-mono leading-relaxed">
|
||||
Recommendation: use a reputable VPN or hidden transport. A VPN can help hide
|
||||
your IP from the backend and peers, but it does not eliminate metadata,
|
||||
endpoint compromise, or traffic analysis risks.
|
||||
@@ -1134,7 +1134,7 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Shield size={12} className="text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span className="text-[10px] text-[var(--text-secondary)] font-mono tracking-widest">
|
||||
<span className="text-sm text-[var(--text-secondary)] font-mono tracking-widest">
|
||||
EPHEMERAL SESSION ID (RECOMMENDED)
|
||||
</span>
|
||||
</div>
|
||||
@@ -1146,21 +1146,21 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
migratePrivacySensitiveBrowserState();
|
||||
if (next) clearSessionIdentity();
|
||||
}}
|
||||
className={`px-2 py-1 border text-[9px] font-mono tracking-widest transition-colors ${sessionMode ? 'border-green-500/40 text-green-400 bg-green-950/20' : 'border-[var(--border-primary)] text-[var(--text-muted)] hover:text-[var(--text-secondary)]'}`}
|
||||
className={`px-2 py-1 border text-[13px] font-mono tracking-widest transition-colors ${sessionMode ? 'border-green-500/40 text-green-400 bg-green-950/20' : 'border-[var(--border-primary)] text-[var(--text-muted)] hover:text-[var(--text-secondary)]'}`}
|
||||
>
|
||||
{sessionMode ? 'ON' : 'OFF'}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-[10px] text-[var(--text-muted)] font-mono leading-relaxed mt-2">
|
||||
<p className="text-sm text-[var(--text-muted)] font-mono leading-relaxed mt-2">
|
||||
When enabled, agent keys are stored in session storage and reset on browser
|
||||
close. Your identity will not persist across restarts.
|
||||
</p>
|
||||
<div className="mt-3 flex items-center justify-between gap-3 border border-[var(--border-primary)] bg-black/20 px-3 py-2">
|
||||
<div className="min-w-0">
|
||||
<div className="text-[10px] font-mono tracking-widest text-[var(--text-secondary)]">
|
||||
<div className="text-sm font-mono tracking-widest text-[var(--text-secondary)]">
|
||||
WIPE LOCAL MESH TRACES
|
||||
</div>
|
||||
<p className="mt-1 text-[10px] font-mono leading-relaxed text-[var(--text-muted)]">
|
||||
<p className="mt-1 text-sm font-mono leading-relaxed text-[var(--text-muted)]">
|
||||
Clears browser-held mesh identities, DM ratchet state, cached contacts, and
|
||||
privacy-sensitive browser storage. The local agent is not shut down.
|
||||
</p>
|
||||
@@ -1170,7 +1170,7 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
void wipeLocalMeshTraces();
|
||||
}}
|
||||
disabled={browserWipeBusy}
|
||||
className={`shrink-0 px-2 py-1 border text-[9px] font-mono tracking-widest transition-colors ${
|
||||
className={`shrink-0 px-2 py-1 border text-[13px] font-mono tracking-widest transition-colors ${
|
||||
browserWipeBusy
|
||||
? 'border-[var(--border-primary)] text-[var(--text-muted)] opacity-60 cursor-not-allowed'
|
||||
: 'border-yellow-500/40 text-yellow-300 bg-yellow-950/20 hover:text-yellow-200'
|
||||
@@ -1181,7 +1181,7 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
</div>
|
||||
{browserWipeMsg && (
|
||||
<div
|
||||
className={`mt-2 text-[10px] font-mono leading-relaxed ${
|
||||
className={`mt-2 text-sm font-mono leading-relaxed ${
|
||||
browserWipeMsg.type === 'ok' ? 'text-green-300' : 'text-red-300'
|
||||
}`}
|
||||
>
|
||||
@@ -1195,25 +1195,25 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Shield size={12} className="text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span className="text-[10px] text-[var(--text-secondary)] font-mono tracking-widest">
|
||||
<span className="text-sm text-[var(--text-secondary)] font-mono tracking-widest">
|
||||
LOCAL MESH AGENT (OPT-IN)
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={toggleWormhole}
|
||||
disabled={wormholeSaving}
|
||||
className={`px-2 py-1 border text-[9px] font-mono tracking-widest transition-colors ${wormholeEnabled ? 'border-green-500/40 text-green-400 bg-green-950/20' : 'border-[var(--border-primary)] text-[var(--text-muted)] hover:text-[var(--text-secondary)]'} ${wormholeSaving ? 'opacity-60 cursor-not-allowed' : ''}`}
|
||||
className={`px-2 py-1 border text-[13px] font-mono tracking-widest transition-colors ${wormholeEnabled ? 'border-green-500/40 text-green-400 bg-green-950/20' : 'border-[var(--border-primary)] text-[var(--text-muted)] hover:text-[var(--text-secondary)]'} ${wormholeSaving ? 'opacity-60 cursor-not-allowed' : ''}`}
|
||||
>
|
||||
{wormholeEnabled ? 'ON' : 'OFF'}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-[10px] text-[var(--text-muted)] font-mono leading-relaxed mt-2">
|
||||
<p className="text-sm text-[var(--text-muted)] font-mono leading-relaxed mt-2">
|
||||
Runs a local mesh agent that handles traffic directly, removing the backend
|
||||
as a central observer. Experimental — does not guarantee privacy or anonymity.
|
||||
</p>
|
||||
<div className="mt-2 grid grid-cols-1 gap-2">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="text-[9px] font-mono text-[var(--text-muted)] tracking-widest">
|
||||
<span className="text-[13px] font-mono text-[var(--text-muted)] tracking-widest">
|
||||
TRANSPORT
|
||||
</span>
|
||||
<select
|
||||
@@ -1222,7 +1222,7 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
setWormholeTransport(e.target.value);
|
||||
setWormholeDirty(true);
|
||||
}}
|
||||
className="bg-[var(--bg-primary)]/60 border border-[var(--border-primary)] px-2 py-1 text-[9px] font-mono text-[var(--text-secondary)]"
|
||||
className="bg-[var(--bg-primary)]/60 border border-[var(--border-primary)] px-2 py-1 text-[13px] font-mono text-[var(--text-secondary)]"
|
||||
>
|
||||
<option value="direct">DIRECT</option>
|
||||
<option value="tor">TOR (SOCKS5)</option>
|
||||
@@ -1242,7 +1242,7 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
setWormholeDirty(true);
|
||||
}}
|
||||
placeholder="SOCKS5 proxy (e.g. 127.0.0.1:9050)"
|
||||
className="w-full bg-black/30 border border-[var(--border-primary)]/40 px-2 py-1 text-[10px] font-mono text-[var(--text-muted)] outline-none focus:border-cyan-500/50"
|
||||
className="w-full bg-black/30 border border-[var(--border-primary)]/40 px-2 py-1 text-sm font-mono text-[var(--text-muted)] outline-none focus:border-cyan-500/50"
|
||||
/>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
<button
|
||||
@@ -1251,7 +1251,7 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
setWormholeSocksProxy('127.0.0.1:9050');
|
||||
setWormholeDirty(true);
|
||||
}}
|
||||
className="px-2 py-1 border border-purple-500/30 text-purple-300 text-[8px] font-mono tracking-widest hover:bg-purple-950/20"
|
||||
className="px-2 py-1 border border-purple-500/30 text-purple-300 text-[12px] font-mono tracking-widest hover:bg-purple-950/20"
|
||||
>
|
||||
TOR 9050
|
||||
</button>
|
||||
@@ -1261,7 +1261,7 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
setWormholeSocksProxy('127.0.0.1:9150');
|
||||
setWormholeDirty(true);
|
||||
}}
|
||||
className="px-2 py-1 border border-purple-500/30 text-purple-300 text-[8px] font-mono tracking-widest hover:bg-purple-950/20"
|
||||
className="px-2 py-1 border border-purple-500/30 text-purple-300 text-[12px] font-mono tracking-widest hover:bg-purple-950/20"
|
||||
>
|
||||
TOR 9150
|
||||
</button>
|
||||
@@ -1271,7 +1271,7 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
setWormholeSocksProxy('127.0.0.1:4447');
|
||||
setWormholeDirty(true);
|
||||
}}
|
||||
className="px-2 py-1 border border-blue-500/30 text-blue-300 text-[8px] font-mono tracking-widest hover:bg-blue-950/20"
|
||||
className="px-2 py-1 border border-blue-500/30 text-blue-300 text-[12px] font-mono tracking-widest hover:bg-blue-950/20"
|
||||
>
|
||||
I2P 4447
|
||||
</button>
|
||||
@@ -1281,13 +1281,13 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
setWormholeSocksProxy('127.0.0.1:1080');
|
||||
setWormholeDirty(true);
|
||||
}}
|
||||
className="px-2 py-1 border border-cyan-500/30 text-cyan-300 text-[8px] font-mono tracking-widest hover:bg-cyan-950/20"
|
||||
className="px-2 py-1 border border-cyan-500/30 text-cyan-300 text-[12px] font-mono tracking-widest hover:bg-cyan-950/20"
|
||||
>
|
||||
MIXNET 1080
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-[9px] font-mono text-[var(--text-muted)] tracking-widest">
|
||||
<span className="text-[13px] font-mono text-[var(--text-muted)] tracking-widest">
|
||||
PROXY DNS
|
||||
</span>
|
||||
<button
|
||||
@@ -1295,12 +1295,12 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
setWormholeSocksDns((prev) => !prev);
|
||||
setWormholeDirty(true);
|
||||
}}
|
||||
className={`px-2 py-1 border text-[9px] font-mono tracking-widest transition-colors ${wormholeSocksDns ? 'border-green-500/40 text-green-400 bg-green-950/20' : 'border-[var(--border-primary)] text-[var(--text-muted)] hover:text-[var(--text-secondary)]'}`}
|
||||
className={`px-2 py-1 border text-[13px] font-mono tracking-widest transition-colors ${wormholeSocksDns ? 'border-green-500/40 text-green-400 bg-green-950/20' : 'border-[var(--border-primary)] text-[var(--text-muted)] hover:text-[var(--text-secondary)]'}`}
|
||||
>
|
||||
{wormholeSocksDns ? 'ON' : 'OFF'}
|
||||
</button>
|
||||
</div>
|
||||
<div className="text-[9px] font-mono text-[var(--text-muted)] leading-relaxed">
|
||||
<div className="text-[13px] font-mono text-[var(--text-muted)] leading-relaxed">
|
||||
Hidden transport requires a local SOCKS5 proxy (Tor/I2P/Mixnet) already
|
||||
running. Save applies the new transport immediately.
|
||||
</div>
|
||||
@@ -1308,10 +1308,10 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
)}
|
||||
<div className="flex items-center justify-between gap-2 border border-green-900/20 bg-black/20 px-2 py-2">
|
||||
<div>
|
||||
<div className="text-[9px] font-mono text-[var(--text-secondary)] tracking-widest">
|
||||
<div className="text-[13px] font-mono text-[var(--text-secondary)] tracking-widest">
|
||||
HIDDEN TRANSPORT MODE
|
||||
</div>
|
||||
<div className="mt-1 text-[9px] font-mono text-[var(--text-muted)] leading-relaxed">
|
||||
<div className="mt-1 text-[13px] font-mono text-[var(--text-muted)] leading-relaxed">
|
||||
Public mesh writes fail closed unless the local agent is active on
|
||||
Tor/I2P/Mixnet. Direct transport is blocked while this is on.
|
||||
</div>
|
||||
@@ -1321,13 +1321,13 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
setWormholeAnonymousMode((prev) => !prev);
|
||||
setWormholeDirty(true);
|
||||
}}
|
||||
className={`px-2 py-1 border text-[9px] font-mono tracking-widest transition-colors ${wormholeAnonymousMode ? 'border-green-500/40 text-green-400 bg-green-950/20' : 'border-[var(--border-primary)] text-[var(--text-muted)] hover:text-[var(--text-secondary)]'}`}
|
||||
className={`px-2 py-1 border text-[13px] font-mono tracking-widest transition-colors ${wormholeAnonymousMode ? 'border-green-500/40 text-green-400 bg-green-950/20' : 'border-[var(--border-primary)] text-[var(--text-muted)] hover:text-[var(--text-secondary)]'}`}
|
||||
>
|
||||
{wormholeAnonymousMode ? 'ON' : 'OFF'}
|
||||
</button>
|
||||
</div>
|
||||
{wormholeAnonymousMode && (
|
||||
<div className="flex flex-col gap-1 text-[9px] font-mono">
|
||||
<div className="flex flex-col gap-1 text-[13px] font-mono">
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className={`px-1.5 py-0.5 border ${anonModeReady ? 'border-green-500/40 text-green-400 bg-green-950/20' : 'border-yellow-500/40 text-yellow-300 bg-yellow-950/10'}`}
|
||||
@@ -1352,7 +1352,7 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
</div>
|
||||
)}
|
||||
{!wormholeAnonymousMode && (
|
||||
<div className="flex flex-col gap-1 text-[9px] font-mono">
|
||||
<div className="flex flex-col gap-1 text-[13px] font-mono">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="px-1.5 py-0.5 border border-orange-500/40 text-orange-300 bg-orange-950/20">
|
||||
{trustModeLabel}
|
||||
@@ -1371,7 +1371,7 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
<button
|
||||
onClick={() => saveWormholeSettings()}
|
||||
disabled={!wormholeDirty || wormholeSaving}
|
||||
className="px-2 py-1 border border-green-500/40 text-green-400 bg-green-950/20 hover:bg-green-950/30 transition-colors text-[9px] font-mono tracking-widest disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
className="px-2 py-1 border border-green-500/40 text-green-400 bg-green-950/20 hover:bg-green-950/30 transition-colors text-[13px] font-mono tracking-widest disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
>
|
||||
{wormholeSaving ? 'SAVING...' : 'SAVE LOCAL AGENT SETTINGS'}
|
||||
</button>
|
||||
@@ -1379,28 +1379,28 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
<button
|
||||
onClick={() => controlWormhole('connect')}
|
||||
disabled={wormholeSaving}
|
||||
className="px-2 py-1 border border-green-500/40 text-green-400 bg-green-950/20 hover:bg-green-950/30 transition-colors text-[9px] font-mono tracking-widest disabled:opacity-40"
|
||||
className="px-2 py-1 border border-green-500/40 text-green-400 bg-green-950/20 hover:bg-green-950/30 transition-colors text-[13px] font-mono tracking-widest disabled:opacity-40"
|
||||
>
|
||||
CONNECT
|
||||
</button>
|
||||
<button
|
||||
onClick={() => controlWormhole('restart')}
|
||||
disabled={wormholeSaving || !wormholeEnabled}
|
||||
className="px-2 py-1 border border-yellow-500/40 text-yellow-300 bg-yellow-950/10 hover:bg-yellow-950/20 transition-colors text-[9px] font-mono tracking-widest disabled:opacity-40"
|
||||
className="px-2 py-1 border border-yellow-500/40 text-yellow-300 bg-yellow-950/10 hover:bg-yellow-950/20 transition-colors text-[13px] font-mono tracking-widest disabled:opacity-40"
|
||||
>
|
||||
RESTART
|
||||
</button>
|
||||
<button
|
||||
onClick={() => controlWormhole('disconnect')}
|
||||
disabled={wormholeSaving || !wormholeEnabled}
|
||||
className="px-2 py-1 border border-red-500/40 text-red-300 bg-red-950/10 hover:bg-red-950/20 transition-colors text-[9px] font-mono tracking-widest disabled:opacity-40"
|
||||
className="px-2 py-1 border border-red-500/40 text-red-300 bg-red-950/10 hover:bg-red-950/20 transition-colors text-[13px] font-mono tracking-widest disabled:opacity-40"
|
||||
>
|
||||
DISCONNECT
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{rnsStatus && (
|
||||
<div className="mt-2 text-[9px] font-mono text-[var(--text-muted)] flex items-center gap-2">
|
||||
<div className="mt-2 text-[13px] font-mono text-[var(--text-muted)] flex items-center gap-2">
|
||||
<span
|
||||
className={`px-1.5 py-0.5 border ${rnsStatus.ready ? 'border-green-500/40 text-green-400 bg-green-950/20' : 'border-yellow-500/40 text-yellow-400 bg-yellow-950/20'}`}
|
||||
>
|
||||
@@ -1412,7 +1412,7 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
</div>
|
||||
)}
|
||||
{wormholeStatus && (
|
||||
<div className="mt-1 space-y-2 text-[9px] font-mono text-[var(--text-muted)]">
|
||||
<div className="mt-1 space-y-2 text-[13px] font-mono text-[var(--text-muted)]">
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className={`px-1.5 py-0.5 border ${
|
||||
@@ -1454,22 +1454,22 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
</span>
|
||||
)}
|
||||
{wormholeStatus.proxy_active && (
|
||||
<span className="text-[8px] text-[var(--text-muted)]">
|
||||
<span className="text-[12px] text-[var(--text-muted)]">
|
||||
proxy {wormholeStatus.proxy_active}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-[9px] leading-relaxed">
|
||||
<div className="text-[13px] leading-relaxed">
|
||||
Public transport identity, gate personas, and the obfuscated DM alias are
|
||||
compartmentalized inside the local agent.
|
||||
</div>
|
||||
{recentPrivateFallback && (
|
||||
<div className="text-[9px] text-red-300/90 leading-relaxed">
|
||||
<div className="text-[13px] text-red-300/90 leading-relaxed">
|
||||
{recentPrivateFallbackReason}
|
||||
</div>
|
||||
)}
|
||||
{wormholeStatus.last_error && (
|
||||
<div className="text-[9px] text-red-300/90 leading-relaxed">
|
||||
<div className="text-[13px] text-red-300/90 leading-relaxed">
|
||||
{wormholeStatus.last_error}
|
||||
</div>
|
||||
)}
|
||||
@@ -1487,7 +1487,7 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
<div className="mx-4 mt-4 p-3 border border-cyan-900/30 bg-cyan-950/10">
|
||||
<div className="flex items-start gap-2">
|
||||
<Shield size={12} className="text-cyan-500 mt-0.5 flex-shrink-0" />
|
||||
<p className="text-[10px] text-[var(--text-secondary)] font-mono leading-relaxed">
|
||||
<p className="text-sm text-[var(--text-secondary)] font-mono leading-relaxed">
|
||||
API keys are stored locally in the backend{' '}
|
||||
<span className="text-cyan-400">.env</span> file. Keys marked with{' '}
|
||||
<Key size={8} className="inline text-yellow-500" /> are required for full
|
||||
@@ -1513,11 +1513,11 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className={`text-[9px] font-mono tracking-widest font-bold px-2 py-0.5 border ${colorClass}`}
|
||||
className={`text-[13px] font-mono tracking-widest font-bold px-2 py-0.5 border ${colorClass}`}
|
||||
>
|
||||
{category.toUpperCase()}
|
||||
</span>
|
||||
<span className="text-[10px] text-[var(--text-muted)] font-mono">
|
||||
<span className="text-sm text-[var(--text-muted)] font-mono">
|
||||
{categoryApis.length}{' '}
|
||||
{categoryApis.length === 1 ? 'service' : 'services'}
|
||||
</span>
|
||||
@@ -1553,16 +1553,16 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
<div className="flex items-center gap-1.5">
|
||||
{api.has_key ? (
|
||||
api.is_set ? (
|
||||
<span className="text-[8px] font-mono px-1.5 py-0.5 border border-green-500/30 text-green-400 bg-green-950/20">
|
||||
<span className="text-[12px] font-mono px-1.5 py-0.5 border border-green-500/30 text-green-400 bg-green-950/20">
|
||||
KEY SET
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-[8px] font-mono px-1.5 py-0.5 border border-yellow-500/30 text-yellow-400 bg-yellow-950/20">
|
||||
<span className="text-[12px] font-mono px-1.5 py-0.5 border border-yellow-500/30 text-yellow-400 bg-yellow-950/20">
|
||||
MISSING
|
||||
</span>
|
||||
)
|
||||
) : (
|
||||
<span className="text-[8px] font-mono px-1.5 py-0.5 border border-[var(--border-primary)] text-[var(--text-muted)]">
|
||||
<span className="text-[12px] font-mono px-1.5 py-0.5 border border-[var(--border-primary)] text-[var(--text-muted)]">
|
||||
PUBLIC
|
||||
</span>
|
||||
)}
|
||||
@@ -1579,7 +1579,7 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-[10px] text-[var(--text-muted)] font-mono leading-relaxed mb-2">
|
||||
<p className="text-sm text-[var(--text-muted)] font-mono leading-relaxed mb-2">
|
||||
{api.description}
|
||||
</p>
|
||||
{api.has_key && (
|
||||
@@ -1597,14 +1597,14 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
<button
|
||||
onClick={() => saveKey(api)}
|
||||
disabled={saving}
|
||||
className="px-3 py-1.5 bg-cyan-500/20 border border-cyan-500/40 text-cyan-400 hover:bg-cyan-500/30 transition-colors text-[10px] font-mono flex items-center gap-1"
|
||||
className="px-3 py-1.5 bg-cyan-500/20 border border-cyan-500/40 text-cyan-400 hover:bg-cyan-500/30 transition-colors text-sm font-mono flex items-center gap-1"
|
||||
>
|
||||
<Save size={10} />
|
||||
{saving ? '...' : 'SAVE'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setEditingId(null)}
|
||||
className="px-2 py-1.5 border border-[var(--border-primary)] text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:border-[var(--border-secondary)] transition-colors text-[10px] font-mono"
|
||||
className="px-2 py-1.5 border border-[var(--border-primary)] text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:border-[var(--border-secondary)] transition-colors text-sm font-mono"
|
||||
>
|
||||
ESC
|
||||
</button>
|
||||
@@ -1637,7 +1637,7 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
|
||||
{/* Footer */}
|
||||
<div className="p-4 border-t border-[var(--border-primary)]/80">
|
||||
<div className="flex items-center justify-between text-[9px] text-[var(--text-muted)] font-mono">
|
||||
<div className="flex items-center justify-between text-[13px] text-[var(--text-muted)] font-mono">
|
||||
<span>{apis.length} REGISTERED APIs</span>
|
||||
<span>{apis.filter((a) => a.has_key).length} KEYS CONFIGURED</span>
|
||||
</div>
|
||||
@@ -1652,7 +1652,7 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
<div className="mx-4 mt-4 p-3 border border-orange-900/30 bg-orange-950/10">
|
||||
<div className="flex items-start gap-2">
|
||||
<Rss size={12} className="text-orange-500 mt-0.5 flex-shrink-0" />
|
||||
<p className="text-[10px] text-[var(--text-secondary)] font-mono leading-relaxed">
|
||||
<p className="text-sm text-[var(--text-secondary)] font-mono leading-relaxed">
|
||||
Configure RSS/Atom feeds for the Threat Intel news panel. Each feed is scored
|
||||
by keyword heuristics and weighted by the priority you set. Up to{' '}
|
||||
<span className="text-orange-400">{MAX_FEEDS}</span> sources.
|
||||
@@ -1682,14 +1682,14 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
<button
|
||||
key={w}
|
||||
onClick={() => updateFeed(idx, 'weight', w)}
|
||||
className={`w-5 h-5 text-[8px] font-mono font-bold border transition-all ${feed.weight === w ? WEIGHT_COLORS[w] + ' bg-black/40' : 'border-[var(--border-primary)]/40 text-[var(--text-muted)]/50 hover:border-[var(--border-secondary)]'}`}
|
||||
className={`w-5 h-5 text-[12px] font-mono font-bold border transition-all ${feed.weight === w ? WEIGHT_COLORS[w] + ' bg-black/40' : 'border-[var(--border-primary)]/40 text-[var(--text-muted)]/50 hover:border-[var(--border-secondary)]'}`}
|
||||
title={WEIGHT_LABELS[w]}
|
||||
>
|
||||
{w}
|
||||
</button>
|
||||
))}
|
||||
<span
|
||||
className={`text-[8px] font-mono ml-1 w-7 ${WEIGHT_COLORS[feed.weight]?.split(' ')[0] || 'text-gray-400'}`}
|
||||
className={`text-[12px] font-mono ml-1 w-7 ${WEIGHT_COLORS[feed.weight]?.split(' ')[0] || 'text-gray-400'}`}
|
||||
>
|
||||
{WEIGHT_LABELS[feed.weight] || 'STD'}
|
||||
</span>
|
||||
@@ -1707,7 +1707,7 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
type="text"
|
||||
value={feed.url}
|
||||
onChange={(e) => updateFeed(idx, 'url', e.target.value)}
|
||||
className="w-full bg-black/30 border border-[var(--border-primary)]/40 px-2 py-1 text-[10px] font-mono text-[var(--text-muted)] outline-none focus:border-cyan-500/50 focus:text-cyan-300 transition-colors"
|
||||
className="w-full bg-black/30 border border-[var(--border-primary)]/40 px-2 py-1 text-sm font-mono text-[var(--text-muted)] outline-none focus:border-cyan-500/50 focus:text-cyan-300 transition-colors"
|
||||
placeholder="https://example.com/rss.xml"
|
||||
/>
|
||||
</div>
|
||||
@@ -1717,7 +1717,7 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
<button
|
||||
onClick={addFeed}
|
||||
disabled={feeds.length >= MAX_FEEDS}
|
||||
className="w-full py-2.5 border border-dashed border-[var(--border-primary)]/60 text-[var(--text-muted)] hover:border-orange-500/50 hover:text-orange-400 hover:bg-orange-950/10 transition-all text-[10px] font-mono flex items-center justify-center gap-1.5 disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
className="w-full py-2.5 border border-dashed border-[var(--border-primary)]/60 text-[var(--text-muted)] hover:border-orange-500/50 hover:text-orange-400 hover:bg-orange-950/10 transition-all text-sm font-mono flex items-center justify-center gap-1.5 disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
>
|
||||
<Plus size={10} />
|
||||
ADD FEED ({feeds.length}/{MAX_FEEDS})
|
||||
@@ -1727,7 +1727,7 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
{/* Status message */}
|
||||
{feedMsg && (
|
||||
<div
|
||||
className={`mx-4 mb-2 px-3 py-2 text-[10px] font-mono ${feedMsg.type === 'ok' ? 'text-green-400 bg-green-950/20 border border-green-900/30' : 'text-red-400 bg-red-950/20 border border-red-900/30'}`}
|
||||
className={`mx-4 mb-2 px-3 py-2 text-sm font-mono ${feedMsg.type === 'ok' ? 'text-green-400 bg-green-950/20 border border-green-900/30' : 'text-red-400 bg-red-950/20 border border-red-900/30'}`}
|
||||
>
|
||||
{feedMsg.text}
|
||||
</div>
|
||||
@@ -1739,21 +1739,21 @@ const SettingsPanel = React.memo(function SettingsPanel({
|
||||
<button
|
||||
onClick={saveFeeds}
|
||||
disabled={!feedsDirty || feedSaving}
|
||||
className="flex-1 px-4 py-2 bg-orange-500/20 border border-orange-500/40 text-orange-400 hover:bg-orange-500/30 transition-colors text-[10px] font-mono flex items-center justify-center gap-1.5 disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
className="flex-1 px-4 py-2 bg-orange-500/20 border border-orange-500/40 text-orange-400 hover:bg-orange-500/30 transition-colors text-sm font-mono flex items-center justify-center gap-1.5 disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
>
|
||||
<Save size={10} />
|
||||
{feedSaving ? 'SAVING...' : 'SAVE FEEDS'}
|
||||
</button>
|
||||
<button
|
||||
onClick={resetFeeds}
|
||||
className="px-3 py-2 border border-[var(--border-primary)] text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:border-[var(--border-secondary)] transition-all text-[10px] font-mono flex items-center gap-1.5"
|
||||
className="px-3 py-2 border border-[var(--border-primary)] text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:border-[var(--border-secondary)] transition-all text-sm font-mono flex items-center gap-1.5"
|
||||
title="Reset to defaults"
|
||||
>
|
||||
<RotateCcw size={10} />
|
||||
RESET
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-[9px] text-[var(--text-muted)] font-mono mt-2">
|
||||
<div className="flex items-center justify-between text-[13px] text-[var(--text-muted)] font-mono mt-2">
|
||||
<span>
|
||||
{feeds.length}/{MAX_FEEDS} SOURCES
|
||||
</span>
|
||||
@@ -1837,7 +1837,7 @@ function SentinelTab() {
|
||||
<div className="mx-4 mt-4 p-3 border border-purple-900/30 bg-purple-950/10">
|
||||
<div className="flex items-start gap-2">
|
||||
<Satellite size={12} className="text-purple-400 mt-0.5 flex-shrink-0" />
|
||||
<div className="text-[10px] text-[var(--text-secondary)] font-mono leading-relaxed space-y-2">
|
||||
<div className="text-sm text-[var(--text-secondary)] font-mono leading-relaxed space-y-2">
|
||||
<p className="text-purple-300 font-bold">COPERNICUS SENTINEL HUB SETUP</p>
|
||||
<p className="text-[var(--text-muted)]">
|
||||
Sentinel Hub gives you access to ESA satellite imagery (Sentinel-2 true color,
|
||||
@@ -1894,7 +1894,7 @@ function SentinelTab() {
|
||||
{/* Credential Inputs */}
|
||||
<div className="p-4 space-y-3">
|
||||
<div>
|
||||
<label className="text-[9px] font-mono text-[var(--text-muted)] tracking-widest mb-1 block">
|
||||
<label className="text-[13px] font-mono text-[var(--text-muted)] tracking-widest mb-1 block">
|
||||
CLIENT ID
|
||||
</label>
|
||||
<input
|
||||
@@ -1911,7 +1911,7 @@ function SentinelTab() {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-[9px] font-mono text-[var(--text-muted)] tracking-widest mb-1 block">
|
||||
<label className="text-[13px] font-mono text-[var(--text-muted)] tracking-widest mb-1 block">
|
||||
CLIENT SECRET
|
||||
</label>
|
||||
<input
|
||||
@@ -1929,7 +1929,7 @@ function SentinelTab() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowSecret((current) => !current)}
|
||||
className="mt-2 inline-flex items-center gap-1.5 text-[9px] font-mono text-[var(--text-muted)] hover:text-[var(--text-secondary)] transition-colors"
|
||||
className="mt-2 inline-flex items-center gap-1.5 text-[13px] font-mono text-[var(--text-muted)] hover:text-[var(--text-secondary)] transition-colors"
|
||||
>
|
||||
{showSecret ? <EyeOff size={10} /> : <Eye size={10} />}
|
||||
{showSecret ? 'HIDE SECRET' : 'SHOW SECRET'}
|
||||
@@ -1940,7 +1940,7 @@ function SentinelTab() {
|
||||
{/* Status */}
|
||||
{status && (
|
||||
<div
|
||||
className={`mx-4 mb-2 px-3 py-2 text-[10px] font-mono ${status.ok ? 'text-green-400 bg-green-950/20 border border-green-900/30' : 'text-red-400 bg-red-950/20 border border-red-900/30'}`}
|
||||
className={`mx-4 mb-2 px-3 py-2 text-sm font-mono ${status.ok ? 'text-green-400 bg-green-950/20 border border-green-900/30' : 'text-red-400 bg-red-950/20 border border-red-900/30'}`}
|
||||
>
|
||||
{status.msg}
|
||||
</div>
|
||||
@@ -1952,7 +1952,7 @@ function SentinelTab() {
|
||||
<button
|
||||
onClick={save}
|
||||
disabled={!dirty}
|
||||
className="flex-1 px-4 py-2 bg-purple-500/20 border border-purple-500/40 text-purple-400 hover:bg-purple-500/30 transition-colors text-[10px] font-mono flex items-center justify-center gap-1.5 disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
className="flex-1 px-4 py-2 bg-purple-500/20 border border-purple-500/40 text-purple-400 hover:bg-purple-500/30 transition-colors text-sm font-mono flex items-center justify-center gap-1.5 disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
>
|
||||
<Save size={10} />
|
||||
SAVE
|
||||
@@ -1960,13 +1960,13 @@ function SentinelTab() {
|
||||
<button
|
||||
onClick={testConnection}
|
||||
disabled={testing || !clientId || !clientSecret}
|
||||
className="flex-1 px-4 py-2 bg-cyan-500/20 border border-cyan-500/40 text-cyan-400 hover:bg-cyan-500/30 transition-colors text-[10px] font-mono flex items-center justify-center gap-1.5 disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
className="flex-1 px-4 py-2 bg-cyan-500/20 border border-cyan-500/40 text-cyan-400 hover:bg-cyan-500/30 transition-colors text-sm font-mono flex items-center justify-center gap-1.5 disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
>
|
||||
{testing ? 'TESTING...' : 'TEST CONNECTION'}
|
||||
</button>
|
||||
<button
|
||||
onClick={clear}
|
||||
className="px-3 py-2 border border-[var(--border-primary)] text-[var(--text-muted)] hover:text-red-400 hover:border-red-500/50 hover:bg-red-950/10 transition-all text-[10px] font-mono flex items-center gap-1.5"
|
||||
className="px-3 py-2 border border-[var(--border-primary)] text-[var(--text-muted)] hover:text-red-400 hover:border-red-500/50 hover:bg-red-950/10 transition-all text-sm font-mono flex items-center gap-1.5"
|
||||
title="Clear credentials"
|
||||
>
|
||||
<Trash2 size={10} />
|
||||
@@ -1976,7 +1976,7 @@ function SentinelTab() {
|
||||
<UsageMeter />
|
||||
|
||||
<div className="mt-2 p-2 border border-[var(--border-primary)]/40 bg-[var(--bg-primary)]/30">
|
||||
<p className="text-[9px] text-[var(--text-muted)] font-mono leading-relaxed">
|
||||
<p className="text-[13px] text-[var(--text-muted)] font-mono leading-relaxed">
|
||||
Credentials stay in browser-only storage and never touch ShadowBroker servers.
|
||||
{storageMode === 'session'
|
||||
? ' Current privacy mode keeps them in session storage only.'
|
||||
@@ -2016,10 +2016,10 @@ function UsageMeter() {
|
||||
return (
|
||||
<div className="mt-3 p-3 border border-purple-900/30 bg-purple-950/10">
|
||||
<div className="flex items-center justify-between mb-1.5">
|
||||
<span className="text-[9px] font-mono text-purple-400 tracking-widest">
|
||||
<span className="text-[13px] font-mono text-purple-400 tracking-widest">
|
||||
MONTHLY USAGE
|
||||
</span>
|
||||
<span className="text-[9px] font-mono text-[var(--text-muted)]">
|
||||
<span className="text-[13px] font-mono text-[var(--text-muted)]">
|
||||
{usage.month || '—'}
|
||||
</span>
|
||||
</div>
|
||||
@@ -2035,7 +2035,7 @@ function UsageMeter() {
|
||||
<div className={`text-[11px] font-mono font-bold ${textColor}`}>
|
||||
{usage.tiles.toLocaleString()}
|
||||
</div>
|
||||
<div className="text-[8px] font-mono text-[var(--text-muted)]">
|
||||
<div className="text-[12px] font-mono text-[var(--text-muted)]">
|
||||
/ {maxRequests.toLocaleString()} tiles
|
||||
</div>
|
||||
</div>
|
||||
@@ -2043,7 +2043,7 @@ function UsageMeter() {
|
||||
<div className={`text-[11px] font-mono font-bold ${textColor}`}>
|
||||
{usage.pu.toLocaleString()}
|
||||
</div>
|
||||
<div className="text-[8px] font-mono text-[var(--text-muted)]">
|
||||
<div className="text-[12px] font-mono text-[var(--text-muted)]">
|
||||
/ {maxPU.toLocaleString()} PU
|
||||
</div>
|
||||
</div>
|
||||
@@ -2051,7 +2051,7 @@ function UsageMeter() {
|
||||
<div className="text-[11px] font-mono font-bold text-[var(--text-secondary)]">
|
||||
{Math.round(100 - pct)}%
|
||||
</div>
|
||||
<div className="text-[8px] font-mono text-[var(--text-muted)]">remaining</div>
|
||||
<div className="text-[12px] font-mono text-[var(--text-muted)]">remaining</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -505,7 +505,7 @@ export default function ShodanPanel({
|
||||
SHODAN CONNECTOR
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-[8px] font-mono">
|
||||
<div className="flex items-center gap-2 text-[12px] font-mono">
|
||||
<span className="border border-green-700/40 px-1.5 py-0.5 text-green-300">
|
||||
{currentResults.length.toLocaleString()} MAP
|
||||
</span>
|
||||
@@ -522,7 +522,7 @@ export default function ShodanPanel({
|
||||
|
||||
{!isMinimized && (
|
||||
<>
|
||||
<div className="border-b border-green-900/40 bg-green-950/10 px-3 py-2 text-[10px] font-mono leading-relaxed text-green-200/90">
|
||||
<div className="border-b border-green-900/40 bg-green-950/10 px-3 py-2 text-sm font-mono leading-relaxed text-green-200/90">
|
||||
<div className="flex items-start gap-2">
|
||||
<AlertTriangle size={12} className="mt-0.5 text-green-400" />
|
||||
<div>
|
||||
@@ -536,7 +536,7 @@ export default function ShodanPanel({
|
||||
</div>
|
||||
|
||||
<div className="px-3 py-2">
|
||||
<div className="mb-2 flex items-center gap-2 text-[9px] font-mono">
|
||||
<div className="mb-2 flex items-center gap-2 text-[13px] font-mono">
|
||||
{(['search', 'count', 'host'] as Mode[]).map((item) => (
|
||||
<button
|
||||
key={item}
|
||||
@@ -559,7 +559,7 @@ export default function ShodanPanel({
|
||||
</div>
|
||||
|
||||
{!status?.configured && (
|
||||
<div className="mb-3 border border-yellow-700/30 bg-yellow-950/10 px-3 py-2 text-[10px] font-mono text-yellow-300">
|
||||
<div className="mb-3 border border-yellow-700/30 bg-yellow-950/10 px-3 py-2 text-sm font-mono text-yellow-300">
|
||||
<div className="mb-2 flex items-center gap-2 font-bold tracking-wide">
|
||||
<KeyRound size={12} /> SHODAN_API_KEY REQUIRED
|
||||
</div>
|
||||
@@ -572,7 +572,7 @@ export default function ShodanPanel({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-2 text-[10px] font-mono">
|
||||
<div className="space-y-2 text-sm font-mono">
|
||||
{mode !== 'host' ? (
|
||||
<>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -616,7 +616,7 @@ export default function ShodanPanel({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-3 flex items-center gap-2 text-[9px] font-mono">
|
||||
<div className="mt-3 flex items-center gap-2 text-[13px] font-mono">
|
||||
{mode === 'search' && (
|
||||
<button
|
||||
onClick={() => void handleSearch()}
|
||||
@@ -655,7 +655,7 @@ export default function ShodanPanel({
|
||||
{/* ── Marker Style Configurator ── */}
|
||||
<div className="mt-3 border border-green-900/40 bg-black/80 px-3 py-2">
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<span className="text-[9px] font-mono tracking-[0.22em] text-green-500">MARKER STYLE</span>
|
||||
<span className="text-[13px] font-mono tracking-[0.22em] text-green-500">MARKER STYLE</span>
|
||||
<span className="text-[14px] leading-none" style={{ color: styleConfig.color }}>
|
||||
{SHAPE_OPTIONS.find((s) => s.value === styleConfig.shape)?.glyph ?? '●'}
|
||||
</span>
|
||||
@@ -663,7 +663,7 @@ export default function ShodanPanel({
|
||||
|
||||
{/* Shape */}
|
||||
<div className="mb-2">
|
||||
<div className="mb-1 text-[8px] font-mono tracking-widest text-green-600">SHAPE</div>
|
||||
<div className="mb-1 text-[12px] font-mono tracking-widest text-green-600">SHAPE</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
{SHAPE_OPTIONS.map((opt) => (
|
||||
<button
|
||||
@@ -684,7 +684,7 @@ export default function ShodanPanel({
|
||||
|
||||
{/* Color */}
|
||||
<div className="mb-2">
|
||||
<div className="mb-1 text-[8px] font-mono tracking-widest text-green-600">COLOR</div>
|
||||
<div className="mb-1 text-[12px] font-mono tracking-widest text-green-600">COLOR</div>
|
||||
<div className="flex items-center gap-1.5 flex-wrap">
|
||||
{COLOR_SWATCHES.map((hex) => (
|
||||
<button
|
||||
@@ -710,20 +710,20 @@ export default function ShodanPanel({
|
||||
}}
|
||||
placeholder="#hex"
|
||||
maxLength={7}
|
||||
className="w-16 border border-green-900/50 bg-black/70 px-1.5 py-0.5 text-[9px] font-mono text-green-300 outline-none focus:border-green-500/60"
|
||||
className="w-16 border border-green-900/50 bg-black/70 px-1.5 py-0.5 text-[13px] font-mono text-green-300 outline-none focus:border-green-500/60"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Size */}
|
||||
<div>
|
||||
<div className="mb-1 text-[8px] font-mono tracking-widest text-green-600">SIZE</div>
|
||||
<div className="mb-1 text-[12px] font-mono tracking-widest text-green-600">SIZE</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
{SIZE_OPTIONS.map((opt) => (
|
||||
<button
|
||||
key={opt.value}
|
||||
onClick={() => updateStyle({ size: opt.value })}
|
||||
className={`px-2.5 py-1 border text-[9px] font-mono tracking-wider transition-colors ${
|
||||
className={`px-2.5 py-1 border text-[13px] font-mono tracking-wider transition-colors ${
|
||||
styleConfig.size === opt.value
|
||||
? 'border-green-500/60 bg-green-950/40 text-green-300'
|
||||
: 'border-green-900/40 text-green-700 hover:border-green-700/60 hover:text-green-400'
|
||||
@@ -737,24 +737,24 @@ export default function ShodanPanel({
|
||||
</div>
|
||||
|
||||
<div className="mt-3 border border-green-900/40 bg-black/80 px-3 py-2">
|
||||
<div className="mb-2 text-[9px] font-mono tracking-[0.22em] text-green-500">PRESETS / EXPORT</div>
|
||||
<div className="mb-2 text-[13px] font-mono tracking-[0.22em] text-green-500">PRESETS / EXPORT</div>
|
||||
<div className="mb-2 flex items-center gap-2">
|
||||
<input
|
||||
value={presetLabel}
|
||||
onChange={(e) => setPresetLabel(e.target.value)}
|
||||
placeholder="preset label"
|
||||
className="flex-1 border border-green-900/50 bg-black/70 px-2 py-1.5 text-[10px] text-green-300 outline-none transition-colors focus:border-green-500/60"
|
||||
className="flex-1 border border-green-900/50 bg-black/70 px-2 py-1.5 text-sm text-green-300 outline-none transition-colors focus:border-green-500/60"
|
||||
/>
|
||||
<button
|
||||
onClick={handleSavePreset}
|
||||
className="border border-green-600/40 px-2 py-1.5 text-[9px] font-mono text-green-400 transition-colors hover:border-green-500/70"
|
||||
className="border border-green-600/40 px-2 py-1.5 text-[13px] font-mono text-green-400 transition-colors hover:border-green-500/70"
|
||||
>
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<Save size={10} /> SAVE
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 text-[9px] font-mono">
|
||||
<div className="flex flex-wrap gap-2 text-[13px] font-mono">
|
||||
<button
|
||||
onClick={exportPresets}
|
||||
disabled={!presets.length}
|
||||
@@ -822,13 +822,13 @@ export default function ShodanPanel({
|
||||
>
|
||||
<button
|
||||
onClick={() => applyPreset(preset)}
|
||||
className="min-w-0 flex-1 truncate text-left text-[10px] font-mono text-green-300 transition-colors hover:text-green-200"
|
||||
className="min-w-0 flex-1 truncate text-left text-sm font-mono text-green-300 transition-colors hover:text-green-200"
|
||||
>
|
||||
{preset.label}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => removePreset(preset.id)}
|
||||
className="ml-2 text-[9px] font-mono text-green-700/70 transition-colors hover:text-red-300"
|
||||
className="ml-2 text-[13px] font-mono text-green-700/70 transition-colors hover:text-red-300"
|
||||
>
|
||||
DELETE
|
||||
</button>
|
||||
@@ -838,7 +838,7 @@ export default function ShodanPanel({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-3 border border-green-900/40 bg-black/80 px-3 py-2 text-[10px] font-mono">
|
||||
<div className="mt-3 border border-green-900/40 bg-black/80 px-3 py-2 text-sm font-mono">
|
||||
<div className="mb-1 flex items-center gap-2 text-green-500">
|
||||
<ShieldAlert size={12} />
|
||||
<span className="tracking-[0.25em]">SESSION STATUS</span>
|
||||
@@ -852,7 +852,7 @@ export default function ShodanPanel({
|
||||
<button
|
||||
onClick={() => { setError(null); lastAction(); }}
|
||||
disabled={busy}
|
||||
className="ml-2 inline-flex shrink-0 items-center gap-1 border border-red-700/40 px-1.5 py-0.5 text-[9px] font-mono text-red-300 transition-colors hover:border-red-500/60 hover:text-red-200 disabled:opacity-40"
|
||||
className="ml-2 inline-flex shrink-0 items-center gap-1 border border-red-700/40 px-1.5 py-0.5 text-[13px] font-mono text-red-300 transition-colors hover:border-red-500/60 hover:text-red-200 disabled:opacity-40"
|
||||
>
|
||||
<RefreshCw size={9} /> RETRY
|
||||
</button>
|
||||
@@ -863,16 +863,16 @@ export default function ShodanPanel({
|
||||
|
||||
{countSummary && (
|
||||
<div className="mt-3 max-h-40 space-y-2 overflow-y-auto border border-green-900/40 bg-black/80 p-3 styled-scrollbar">
|
||||
<div className="text-[9px] font-mono tracking-[0.22em] text-green-500">FACETS</div>
|
||||
<div className="text-[13px] font-mono tracking-[0.22em] text-green-500">FACETS</div>
|
||||
{Object.entries(countSummary.facets).length === 0 ? (
|
||||
<div className="text-[10px] font-mono text-green-300/80">No facet buckets returned.</div>
|
||||
<div className="text-sm font-mono text-green-300/80">No facet buckets returned.</div>
|
||||
) : (
|
||||
Object.entries(countSummary.facets).map(([name, buckets]) => (
|
||||
<div key={name}>
|
||||
<div className="mb-1 text-[9px] font-mono text-green-400">{name.toUpperCase()}</div>
|
||||
<div className="mb-1 text-[13px] font-mono text-green-400">{name.toUpperCase()}</div>
|
||||
<div className="space-y-1">
|
||||
{buckets.map((bucket) => (
|
||||
<div key={`${name}-${bucket.value}`} className="flex items-center justify-between text-[10px] font-mono text-green-300/90">
|
||||
<div key={`${name}-${bucket.value}`} className="flex items-center justify-between text-sm font-mono text-green-300/90">
|
||||
<span className="truncate pr-3">{bucket.value || 'UNKNOWN'}</span>
|
||||
<span>{bucket.count.toLocaleString()}</span>
|
||||
</div>
|
||||
@@ -885,7 +885,7 @@ export default function ShodanPanel({
|
||||
)}
|
||||
|
||||
{hostSummary && (
|
||||
<div className="mt-3 max-h-40 overflow-y-auto border border-green-900/40 bg-black/80 p-3 styled-scrollbar text-[10px] font-mono">
|
||||
<div className="mt-3 max-h-40 overflow-y-auto border border-green-900/40 bg-black/80 p-3 styled-scrollbar text-sm font-mono">
|
||||
<div className="mb-2 flex items-center justify-between text-green-400">
|
||||
<span>{hostSummary.ip}</span>
|
||||
<span>{hostSummary.location_label || 'UNMAPPED'}</span>
|
||||
@@ -905,7 +905,7 @@ export default function ShodanPanel({
|
||||
|
||||
{currentResults.length > 0 && (
|
||||
<div className="mt-3 max-h-44 overflow-y-auto border border-green-900/40 bg-black/80 p-2 styled-scrollbar">
|
||||
<div className="mb-2 flex items-center justify-between text-[9px] font-mono text-green-500">
|
||||
<div className="mb-2 flex items-center justify-between text-[13px] font-mono text-green-500">
|
||||
<span className="tracking-[0.22em]">MAPPED HOSTS</span>
|
||||
<span>{currentResults.length.toLocaleString()}</span>
|
||||
</div>
|
||||
@@ -917,15 +917,15 @@ export default function ShodanPanel({
|
||||
className="flex w-full items-center justify-between border border-green-950/40 bg-green-950/10 px-2 py-1.5 text-left transition-colors hover:border-green-700/60 hover:bg-green-950/20"
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<div className="truncate text-[10px] font-mono text-green-300">
|
||||
<div className="truncate text-sm font-mono text-green-300">
|
||||
{match.ip}
|
||||
{match.port ? `:${match.port}` : ''}
|
||||
</div>
|
||||
<div className="truncate text-[9px] font-mono text-green-600">
|
||||
<div className="truncate text-[13px] font-mono text-green-600">
|
||||
{match.location_label || match.org || 'UNMAPPED'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-3 shrink-0 text-[8px] font-mono text-green-500">
|
||||
<div className="ml-3 shrink-0 text-[12px] font-mono text-green-500">
|
||||
{match.product || match.transport || 'HOST'}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user