setSearchQuery(e.target.value)}
- className="h-8 text-sm pr-8"
- />
- {searchQuery && (
-
- {/* Raw Packet Feed */}
- {!query && (
-
- handleSelectConversation({
- type: 'raw',
- id: 'raw',
- name: 'Raw Packet Feed',
- })
- }
- >
- π‘
- Packet Feed
-
- )}
+ {/* Quick nav - special views */}
+ {!query && (
+
+ {[
+ {
+ type: 'raw' as const,
+ id: 'raw',
+ name: 'Raw Packet Feed',
+ icon: Radio,
+ title: 'Packet Feed',
+ },
+ { type: 'map' as const, id: 'map', name: 'Node Map', icon: Map, title: 'Node Map' },
+ {
+ type: 'visualizer' as const,
+ id: 'visualizer',
+ name: 'Mesh Visualizer',
+ icon: Sparkles,
+ title: 'Visualizer',
+ },
+ ].map((view) => (
+
+ ))}
+
+ )}
- {/* Node Map */}
- {!query && (
-
+
- )}
-
- {/* Mesh Visualizer */}
- {!query && (
-
- handleSelectConversation({
- type: 'visualizer',
- id: 'visualizer',
- name: 'Mesh Visualizer',
- })
- }
- >
- β¨
- Mesh Visualizer
-
- )}
-
- {/* Cracker Toggle */}
- {!query && (
-
- π
-
- {showCracker ? 'Hide' : 'Show'} Room Finder
-
- ({crackerRunning ? 'running' : 'stopped'})
-
+
+
+ Finder
+ {crackerRunning && (
+
+ )}
-
- )}
-
- {/* Mark All Read */}
- {!query && Object.keys(unreadCounts).length > 0 && (
-
- β
- Mark all as read
-
- )}
+
+ {Object.keys(unreadCounts).length > 0 && (
+
+ )}
+
{/* Favorites */}
{favoriteItems.length > 0 && (
<>
-
-
Favorites
+
+
+
+ Favorites
+
{favoriteItems.map((item) => {
if (item.type === 'channel') {
const channel = item.channel;
- const unreadCount = getUnreadCount('channel', channel.key);
- const isMention = hasMention('channel', channel.key);
+ const count = getUnreadCount('channel', channel.key);
+ const mention = hasMention('channel', channel.key);
return (
-
0 && '[&_.name]:font-bold [&_.name]:text-foreground'
- )}
+ active={isActive('channel', channel.key)}
+ unreadCount={count}
+ isMentionItem={mention}
onClick={() =>
handleSelectConversation({
type: 'channel',
@@ -368,34 +405,21 @@ export function Sidebar({
name: channel.name,
})
}
+ icon={}
>
- {channel.name}
- {unreadCount > 0 && (
-
- {unreadCount}
-
- )}
-
+ {channel.name}
+
);
} else {
const contact = item.contact;
- const unreadCount = getUnreadCount('contact', contact.public_key);
- const isMention = hasMention('contact', contact.public_key);
+ const count = getUnreadCount('contact', contact.public_key);
+ const mention = hasMention('contact', contact.public_key);
return (
-
0 && '[&_.name]:font-bold [&_.name]:text-foreground'
- )}
+ active={isActive('contact', contact.public_key)}
+ unreadCount={count}
+ isMentionItem={mention}
onClick={() =>
handleSelectConversation({
type: 'contact',
@@ -403,29 +427,17 @@ export function Sidebar({
name: getContactDisplayName(contact.name, contact.public_key),
})
}
+ icon={
+
+ }
>
-
-
- {getContactDisplayName(contact.name, contact.public_key)}
-
- {unreadCount > 0 && (
-
- {unreadCount}
-
- )}
-
+ {getContactDisplayName(contact.name, contact.public_key)}
+
);
}
})}
@@ -435,27 +447,30 @@ export function Sidebar({
{/* Channels */}
{nonFavoriteChannels.length > 0 && (
<>
-
-
Channels
+
+
+
+
+ Channels
+
+
{nonFavoriteChannels.map((channel) => {
- const unreadCount = getUnreadCount('channel', channel.key);
- const isMention = hasMention('channel', channel.key);
+ const count = getUnreadCount('channel', channel.key);
+ const mention = hasMention('channel', channel.key);
return (
-
0 && '[&_.name]:font-bold [&_.name]:text-foreground'
- )}
+ active={isActive('channel', channel.key)}
+ unreadCount={count}
+ isMentionItem={mention}
onClick={() =>
handleSelectConversation({
type: 'channel',
@@ -463,21 +478,10 @@ export function Sidebar({
name: channel.name,
})
}
+ icon={}
>
- {channel.name}
- {unreadCount > 0 && (
-
- {unreadCount}
-
- )}
-
+ {channel.name}
+
);
})}
>
@@ -486,29 +490,32 @@ export function Sidebar({
{/* Contacts */}
{nonFavoriteContacts.length > 0 && (
<>
-
-
Contacts
+
+
+
+
+ Contacts
+
+
{nonFavoriteChannels.length === 0 && (
)}
{nonFavoriteContacts.map((contact) => {
- const unreadCount = getUnreadCount('contact', contact.public_key);
- const isMention = hasMention('contact', contact.public_key);
+ const count = getUnreadCount('contact', contact.public_key);
+ const mention = hasMention('contact', contact.public_key);
return (
-
0 && '[&_.name]:font-bold [&_.name]:text-foreground'
- )}
+ active={isActive('contact', contact.public_key)}
+ unreadCount={count}
+ isMentionItem={mention}
onClick={() =>
handleSelectConversation({
type: 'contact',
@@ -516,29 +523,17 @@ export function Sidebar({
name: getContactDisplayName(contact.name, contact.public_key),
})
}
+ icon={
+
+ }
>
-
-
- {getContactDisplayName(contact.name, contact.public_key)}
-
- {unreadCount > 0 && (
-
- {unreadCount}
-
- )}
-
+ {getContactDisplayName(contact.name, contact.public_key)}
+
);
})}
>
@@ -548,7 +543,7 @@ export function Sidebar({
{nonFavoriteContacts.length === 0 &&
nonFavoriteChannels.length === 0 &&
favoriteItems.length === 0 && (
-
+
{query ? 'No matches found' : 'No conversations yet'}
)}
diff --git a/frontend/src/components/StatusBar.tsx b/frontend/src/components/StatusBar.tsx
index 36f7774..281c125 100644
--- a/frontend/src/components/StatusBar.tsx
+++ b/frontend/src/components/StatusBar.tsx
@@ -1,5 +1,6 @@
import { useState } from 'react';
-import { Menu } from 'lucide-react';
+import { motion, AnimatePresence } from 'framer-motion';
+import { Menu, Radio, Settings, MessageSquare, Wifi, WifiOff } from 'lucide-react';
import type { HealthStatus, RadioConfig } from '../types';
import { api } from '../api';
import { toast } from './ui/sonner';
@@ -39,61 +40,111 @@ export function StatusBar({
};
return (
-
- {/* Mobile menu button - only visible on small screens */}
+
+ {/* Subtle gradient overlay */}
+
+
+ {/* Mobile menu button */}
{onMenuClick && (
)}
-
RemoteTerm
-
-
-
-
- {connected ? 'Connected' : 'Disconnected'}
-
+ {/* Logo / Title */}
+
+
+
+ {connected && (
+
+ )}
+
+
RemoteTerm
+ {/* Connection status */}
+
+
+ {connected ? (
+
+
+
+ Connected
+
+
+ ) : (
+
+
+
+ Offline
+
+
+ )}
+
+
+
+ {/* Radio info */}
{config && (
-
-
{config.name || 'Unnamed'}
+
+ {config.name || 'Unnamed'}
{
navigator.clipboard.writeText(config.public_key);
toast.success('Public key copied!');
}}
title="Click to copy public key"
>
- {config.public_key.toLowerCase()}
+ {config.public_key.toLowerCase().slice(0, 16)}...
)}
- {!connected && (
+ {/* Action buttons */}
+
+ {!connected && (
+
+ {reconnecting ? 'Reconnecting...' : 'Reconnect'}
+
+ )}
- )}
-
+
);
}
diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx
index af90aca..b11c2a6 100644
--- a/frontend/src/components/ui/button.tsx
+++ b/frontend/src/components/ui/button.tsx
@@ -5,21 +5,25 @@ import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
- 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
+ 'inline-flex items-center justify-center whitespace-nowrap text-sm font-medium ring-offset-background transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-40 active:scale-[0.97]',
{
variants: {
variant: {
- default: 'bg-primary text-primary-foreground hover:bg-primary/90',
- destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
- outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
- secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
- ghost: 'hover:bg-accent hover:text-accent-foreground',
+ default:
+ 'bg-primary text-primary-foreground hover:bg-primary/90 shadow-glow-amber-sm hover:shadow-glow-amber rounded-lg font-semibold',
+ destructive:
+ 'bg-destructive text-destructive-foreground hover:bg-destructive/90 rounded-lg',
+ outline:
+ 'border border-border bg-transparent hover:bg-secondary hover:border-primary/30 rounded-lg',
+ secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80 rounded-lg',
+ ghost: 'hover:bg-secondary hover:text-foreground rounded-lg',
link: 'text-primary underline-offset-4 hover:underline',
+ glow: 'bg-gradient-to-r from-amber-500 to-amber-600 text-primary-foreground hover:from-amber-400 hover:to-amber-500 shadow-glow-amber hover:shadow-glow-amber rounded-xl font-semibold',
},
size: {
- default: 'h-10 px-4 py-2',
- sm: 'h-9 rounded-md px-3',
- lg: 'h-11 rounded-md px-8',
+ default: 'h-10 px-5 py-2',
+ sm: 'h-8 rounded-lg px-3 text-xs',
+ lg: 'h-12 rounded-xl px-8 text-base',
icon: 'h-10 w-10',
},
},
diff --git a/frontend/src/components/ui/dialog.tsx b/frontend/src/components/ui/dialog.tsx
index 58332bc..c8be903 100644
--- a/frontend/src/components/ui/dialog.tsx
+++ b/frontend/src/components/ui/dialog.tsx
@@ -21,7 +21,7 @@ const DialogOverlay = React.forwardRef<
>(
(({ className, ...props }, ref) => (
{
toastOptions={{
classNames: {
toast:
- 'group toast group-[.toaster]:bg-card group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
+ 'group toast group-[.toaster]:bg-card/95 group-[.toaster]:backdrop-blur-xl group-[.toaster]:text-foreground group-[.toaster]:border-border/50 group-[.toaster]:shadow-2xl group-[.toaster]:rounded-xl',
description: 'group-[.toast]:text-muted-foreground',
actionButton: 'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
cancelButton: 'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',
- // Muted error style - dark red-tinted background with readable text
error:
- 'group-[.toaster]:bg-[#2a1a1a] group-[.toaster]:text-[#e8a0a0] group-[.toaster]:border-[#4a2a2a] [&_[data-description]]:text-[#b08080]',
+ 'group-[.toaster]:bg-[#1a0e10]/95 group-[.toaster]:text-red-300 group-[.toaster]:border-red-500/20 [&_[data-description]]:text-red-400/70',
+ success:
+ 'group-[.toaster]:bg-[#0e1a12]/95 group-[.toaster]:text-emerald-300 group-[.toaster]:border-emerald-500/20 [&_[data-description]]:text-emerald-400/70',
},
}}
{...props}
diff --git a/frontend/src/components/ui/tabs.tsx b/frontend/src/components/ui/tabs.tsx
index 7315537..e3c5478 100644
--- a/frontend/src/components/ui/tabs.tsx
+++ b/frontend/src/components/ui/tabs.tsx
@@ -14,7 +14,7 @@ const TabsList = React.forwardRef<