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:
anoracleofra-code
2026-03-26 10:38:33 -06:00
parent 4897a54803
commit 04939ee6e8
19 changed files with 553 additions and 553 deletions
@@ -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">&times;</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
+93 -93
View File
@@ -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>
)}
+17 -17
View File
@@ -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>
+102 -102
View File
@@ -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 &amp; 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>
+29 -29
View File
@@ -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>