feat: Improve UI navigation and logo visibility

- Update navigation labels: 'Navigation' → 'Ponderants', 'Edit Node' → 'Manual', 'Conversation' → 'Convo', 'Galaxy View' → 'Galaxy'
- Improve logo visibility with CSS transform scale(3.5) and higher contrast colors (#E0E0E0, #909296)
- Make logo square (viewBox 60x60) for better favicon display
- Enhance mobile UX with vertical navigation buttons and text labels
- Add 'Profile' label to user menu in navigation
- Improve sidebar highlighting with blue color, bold font, and rounded corners
- Fix layout issues: adjust chat padding for sidebar (260px) and bottom bar (90px)
- Make borders more subtle (#373A40 instead of #dee2e6)
- Increase sidebar width to 260px to accommodate logo and text
- Remove desktop top bar for cleaner layout
- Use IconChartBubbleFilled for galaxy navigation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-09 15:00:52 +00:00
parent 0ed2d6c0b3
commit 032f6bc4be
6 changed files with 125 additions and 121 deletions

View File

@@ -167,57 +167,9 @@ export default function ChatPage() {
const canSkipAudio = state.hasTag('canSkipAudio'); const canSkipAudio = state.hasTag('canSkipAudio');
return ( return (
<Container size="md" style={{ paddingTop: '80px', paddingBottom: '300px', maxWidth: '100%' }}> <Container size="md" style={{ paddingTop: isMobile ? '0' : '1rem', paddingBottom: '300px', maxWidth: '100%', height: '100vh' }}>
{/* Fixed Header */}
<Paper
withBorder
p="md"
radius={0}
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
zIndex: 50,
borderBottom: '1px solid #dee2e6',
backgroundColor: '#1a1b1e',
}}
>
<Container size="md">
<Group justify="space-between">
<Title order={2}>Convo</Title>
{!isMobile && (
<Group gap="md">
<Tooltip label="Generate a node from this conversation">
<Button
variant="light"
color="blue"
leftSection={<IconNotes size={18} />}
onClick={handleCreateNode}
loading={isCreatingNode}
disabled={messages.length === 0 || status === 'submitted' || status === 'streaming'}
>
Create Node
</Button>
</Tooltip>
<Tooltip label="Start a new conversation">
<Button
variant="subtle"
onClick={handleNewConversation}
disabled={status === 'submitted' || status === 'streaming'}
>
New Conversation
</Button>
</Tooltip>
<UserMenu />
</Group>
)}
</Group>
</Container>
</Paper>
{/* Scrollable Messages Area */} {/* Scrollable Messages Area */}
<ScrollArea h="calc(100vh - 380px)" viewportRef={viewport}> <ScrollArea h={isMobile ? 'calc(100vh - 250px)' : 'calc(100vh - 300px)'} viewportRef={viewport}>
<Stack gap="md" pb="xl"> <Stack gap="md" pb="xl">
{messages.map((m) => ( {messages.map((m) => (
<Paper <Paper
@@ -301,11 +253,11 @@ export default function ChatPage() {
radius={0} radius={0}
style={{ style={{
position: 'fixed', position: 'fixed',
bottom: 0, bottom: isMobile ? '90px' : 0,
left: 0, left: isMobile ? 0 : '260px',
right: 0, right: 0,
zIndex: 50, zIndex: 50,
borderTop: '1px solid #dee2e6', borderTop: '1px solid #373A40',
backgroundColor: '#1a1b1e', backgroundColor: '#1a1b1e',
}} }}
> >

View File

@@ -26,7 +26,7 @@ export function AppLayout({ children }: { children: React.ReactNode }) {
<AppShell <AppShell
navbar={{ navbar={{
width: isMobile ? 0 : 200, width: isMobile ? 0 : 260,
breakpoint: 'sm', breakpoint: 'sm',
}} }}
padding={isMobile ? 0 : 'md'} padding={isMobile ? 0 : 'md'}
@@ -44,8 +44,8 @@ export function AppLayout({ children }: { children: React.ReactNode }) {
style={{ style={{
height: '100vh', height: '100vh',
overflow: 'auto', overflow: 'auto',
paddingTop: isMobile ? '64px' : '0', // Space for mobile header paddingTop: isMobile ? '72px' : '0', // Space for mobile header
paddingBottom: isMobile ? '80px' : '0', // Space for mobile bottom bar paddingBottom: isMobile ? '90px' : '0', // Space for mobile bottom bar
}} }}
> >
{children} {children}

View File

@@ -9,7 +9,7 @@
*/ */
import { Stack, NavLink, Box, Text, Group, Image, Divider } from '@mantine/core'; import { Stack, NavLink, Box, Text, Group, Image, Divider } from '@mantine/core';
import { IconMessageCircle, IconEdit, IconUniverse } from '@tabler/icons-react'; import { IconMessageCircle, IconEdit, IconChartBubbleFilled } from '@tabler/icons-react';
import { useSelector } from '@xstate/react'; import { useSelector } from '@xstate/react';
import { useAppMachine } from '@/hooks/useAppMachine'; import { useAppMachine } from '@/hooks/useAppMachine';
import { UserMenu } from '@/components/UserMenu'; import { UserMenu } from '@/components/UserMenu';
@@ -46,20 +46,20 @@ export function DesktopSidebar() {
style={{ style={{
width: '100%', width: '100%',
height: '100%', height: '100%',
borderRight: '1px solid #dee2e6', borderRight: '1px solid #373A40',
padding: '1rem', padding: '1rem',
}} }}
> >
<Stack gap="xs"> <Stack gap="xs">
<Group gap="sm" mb="md" align="center"> <Group gap="sm" mb="lg" align="center" wrap="nowrap">
<Image <Box w={32} h={32} style={{ flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
src="/logo.svg" <Image
alt="Ponderants logo" src="/logo.svg"
w={48} alt="Ponderants logo"
h={48} style={{ width: '100%', height: '100%', transform: 'scale(3.5)' }}
style={{ flexShrink: 0 }} />
/> </Box>
<Text fw={700} size="md" c="dimmed"> <Text fw={700} size="xl" style={{ lineHeight: 1 }}>
Ponderants Ponderants
</Text> </Text>
</Group> </Group>
@@ -70,6 +70,13 @@ export function DesktopSidebar() {
active={isConvo} active={isConvo}
onClick={() => handleNavigation('convo')} onClick={() => handleNavigation('convo')}
variant="filled" variant="filled"
color="blue"
styles={{
root: {
borderRadius: '8px',
fontWeight: isConvo ? 600 : 400,
},
}}
/> />
<NavLink <NavLink
@@ -78,21 +85,37 @@ export function DesktopSidebar() {
active={isEdit} active={isEdit}
onClick={() => handleNavigation('edit')} onClick={() => handleNavigation('edit')}
variant="filled" variant="filled"
color="blue"
styles={{
root: {
borderRadius: '8px',
fontWeight: isEdit ? 600 : 400,
},
}}
/> />
<NavLink <NavLink
label="Galaxy" label="Galaxy"
leftSection={<IconUniverse size={20} />} leftSection={<IconChartBubbleFilled size={20} />}
active={isGalaxy} active={isGalaxy}
onClick={() => handleNavigation('galaxy')} onClick={() => handleNavigation('galaxy')}
variant="filled" variant="filled"
color="blue"
styles={{
root: {
borderRadius: '8px',
fontWeight: isGalaxy ? 600 : 400,
},
}}
/> />
<Divider my="md" /> <Divider my="md" color="#373A40" />
<Box style={{ padding: '0.5rem' }}> <NavLink
<UserMenu /> label="Profile"
</Box> leftSection={<Box style={{ width: '40px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}><UserMenu /></Box>}
variant="filled"
/>
{/* Development state panel */} {/* Development state panel */}
{process.env.NODE_ENV === 'development' && ( {process.env.NODE_ENV === 'development' && (

View File

@@ -8,16 +8,38 @@
* Highlights the active mode based on app state machine. * Highlights the active mode based on app state machine.
*/ */
import { Group, Button, Paper, ActionIcon, Box } from '@mantine/core'; import { Group, Button, Paper, ActionIcon, Box, Stack, Text } from '@mantine/core';
import { IconMessageCircle, IconEdit, IconUniverse, IconUser } from '@tabler/icons-react'; import { IconMessageCircle, IconEdit, IconChartBubbleFilled, IconUser } from '@tabler/icons-react';
import { useSelector } from '@xstate/react'; import { useSelector } from '@xstate/react';
import { useAppMachine } from '@/hooks/useAppMachine'; import { useAppMachine } from '@/hooks/useAppMachine';
import { UserMenu } from '@/components/UserMenu'; import { UserMenu } from '@/components/UserMenu';
import { useState, useEffect } from 'react';
interface UserProfile {
did: string;
handle: string;
displayName: string | null;
avatar: string | null;
}
export function MobileBottomBar() { export function MobileBottomBar() {
const actor = useAppMachine(); const actor = useAppMachine();
const state = useSelector(actor, (state) => state); const state = useSelector(actor, (state) => state);
const send = actor.send; const send = actor.send;
const [profile, setProfile] = useState<UserProfile | null>(null);
useEffect(() => {
fetch('/api/user/profile')
.then((res) => res.json())
.then((data) => {
if (!data.error) {
setProfile(data);
}
})
.catch((error) => {
console.error('Failed to fetch profile:', error);
});
}, []);
const handleNavigation = (target: 'convo' | 'edit' | 'galaxy') => { const handleNavigation = (target: 'convo' | 'edit' | 'galaxy') => {
console.log('[Mobile Nav] Navigating to:', target); console.log('[Mobile Nav] Navigating to:', target);
@@ -52,42 +74,49 @@ export function MobileBottomBar() {
left: 0, left: 0,
right: 0, right: 0,
zIndex: 100, zIndex: 100,
borderTop: '1px solid #dee2e6', borderTop: '1px solid #373A40',
}} }}
> >
<Group justify="space-around" grow> <Group justify="space-around" grow>
<ActionIcon <Stack gap={4} align="center" onClick={() => handleNavigation('convo')} style={{ cursor: 'pointer' }}>
variant={isConvo ? 'filled' : 'subtle'} <ActionIcon
color={isConvo ? 'blue' : 'gray'} variant={isConvo ? 'filled' : 'subtle'}
onClick={() => handleNavigation('convo')} color={isConvo ? 'blue' : 'gray'}
size={48} size={40}
radius="md" radius="md"
> >
<IconMessageCircle size={24} /> <IconMessageCircle size={20} />
</ActionIcon> </ActionIcon>
<Text size="xs" c={isConvo ? 'blue' : 'dimmed'}>Convo</Text>
</Stack>
<ActionIcon <Stack gap={4} align="center" onClick={() => handleNavigation('edit')} style={{ cursor: 'pointer' }}>
variant={isEdit ? 'filled' : 'subtle'} <ActionIcon
color={isEdit ? 'blue' : 'gray'} variant={isEdit ? 'filled' : 'subtle'}
onClick={() => handleNavigation('edit')} color={isEdit ? 'blue' : 'gray'}
size={48} size={40}
radius="md" radius="md"
> >
<IconEdit size={24} /> <IconEdit size={20} />
</ActionIcon> </ActionIcon>
<Text size="xs" c={isEdit ? 'blue' : 'dimmed'}>Manual</Text>
</Stack>
<ActionIcon <Stack gap={4} align="center" onClick={() => handleNavigation('galaxy')} style={{ cursor: 'pointer' }}>
variant={isGalaxy ? 'filled' : 'subtle'} <ActionIcon
color={isGalaxy ? 'blue' : 'gray'} variant={isGalaxy ? 'filled' : 'subtle'}
onClick={() => handleNavigation('galaxy')} color={isGalaxy ? 'blue' : 'gray'}
size={48} size={40}
radius="md" radius="md"
> >
<IconUniverse size={24} /> <IconChartBubbleFilled size={20} />
</ActionIcon> </ActionIcon>
<Text size="xs" c={isGalaxy ? 'blue' : 'dimmed'}>Galaxy</Text>
</Stack>
<Box style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}> <Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '4px' }}>
<UserMenu /> <UserMenu />
<Text size="xs" c="dimmed">Profile</Text>
</Box> </Box>
</Group> </Group>
</Paper> </Paper>

View File

@@ -6,13 +6,13 @@
* Fixed header for mobile devices showing the Ponderants logo. * Fixed header for mobile devices showing the Ponderants logo.
*/ */
import { Group, Image, Text, Paper } from '@mantine/core'; import { Group, Image, Text, Paper, Box } from '@mantine/core';
export function MobileHeader() { export function MobileHeader() {
return ( return (
<Paper <Paper
withBorder withBorder
p="md" p="sm"
radius={0} radius={0}
style={{ style={{
position: 'fixed', position: 'fixed',
@@ -20,18 +20,18 @@ export function MobileHeader() {
left: 0, left: 0,
right: 0, right: 0,
zIndex: 100, zIndex: 100,
borderBottom: '1px solid #dee2e6', borderBottom: '1px solid #373A40',
}} }}
> >
<Group gap="sm" align="center"> <Group gap="sm" align="center">
<Image <Box w={32} h={32} style={{ flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
src="/logo.svg" <Image
alt="Ponderants logo" src="/logo.svg"
w={56} alt="Ponderants logo"
h={56} style={{ width: '100%', height: '100%', transform: 'scale(3.5)' }}
style={{ flexShrink: 0 }} />
/> </Box>
<Text fw={700} size="xl"> <Text fw={700} size="lg">
Ponderants Ponderants
</Text> </Text>
</Group> </Group>

View File

@@ -1,15 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 100" width="256" height="256" aria-labelledby="logoTitle"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60" width="256" height="256" aria-labelledby="logoTitle">
<title id="logoTitle">Woven abstract logo of communication waves (Vertical)</title> <title id="logoTitle">Woven abstract logo of communication waves</title>
<g fill-rule="evenodd" fill="none" transform="translate(-18, 20) rotate(90, 48, 30) translate(0, 60) scale(1, -1)"> <g fill-rule="evenodd" fill="none" transform="translate(-18, 0) rotate(90, 48, 30) translate(0, 40) scale(1, -1)">
<!-- Jagged wave - Left end segment (draw first, appears behind) --><path stroke="#AAAAAA" stroke-width="1" stroke-linecap="round" fill="none" d="M 42 30 L 45 26.25" /> <!-- Jagged wave - Left end segment (draw first, appears behind) --><path stroke="#E0E0E0" stroke-width="1.5" stroke-linecap="round" fill="none" d="M 42 30 L 45 26.25" />
<!-- Jagged wave - Right end segment (draw second, appears behind) --><path stroke="#AAAAAA" stroke-width="1" stroke-linecap="round" fill="none" d="M 51 33.75 L 54 30" /> <!-- Jagged wave - Right end segment (draw second, appears behind) --><path stroke="#E0E0E0" stroke-width="1.5" stroke-linecap="round" fill="none" d="M 51 33.75 L 54 30" />
<!-- Curved wave (drawn third, appears in middle layer) --><path stroke="#444444" stroke-width="1" stroke-linecap="round" fill="none" d="M 42 30 Q 45 37.5, 48 30 Q 51 22.5, 54 30" /> <!-- Curved wave (drawn third, appears in middle layer) --><path stroke="#909296" stroke-width="1.5" stroke-linecap="round" fill="none" d="M 42 30 Q 45 37.5, 48 30 Q 51 22.5, 54 30" />
<!-- Jagged wave - Center segment (drawn last, appears in front) --><path stroke="#AAAAAA" stroke-width="1" stroke-linecap="round" fill="none" d="M 45 26.25 L 48 30 L 51 33.75" /> <!-- Jagged wave - Center segment (drawn last, appears in front) --><path stroke="#E0E0E0" stroke-width="1.5" stroke-linecap="round" fill="none" d="M 45 26.25 L 48 30 L 51 33.75" />
</g> </g>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB