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:
@@ -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',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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">
|
||||||
|
<Box w={32} h={32} style={{ flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||||
<Image
|
<Image
|
||||||
src="/logo.svg"
|
src="/logo.svg"
|
||||||
alt="Ponderants logo"
|
alt="Ponderants logo"
|
||||||
w={48}
|
style={{ width: '100%', height: '100%', transform: 'scale(3.5)' }}
|
||||||
h={48}
|
|
||||||
style={{ flexShrink: 0 }}
|
|
||||||
/>
|
/>
|
||||||
<Text fw={700} size="md" c="dimmed">
|
</Box>
|
||||||
|
<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' && (
|
||||||
|
|||||||
@@ -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>
|
||||||
|
<Stack gap={4} align="center" onClick={() => handleNavigation('convo')} style={{ cursor: 'pointer' }}>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant={isConvo ? 'filled' : 'subtle'}
|
variant={isConvo ? 'filled' : 'subtle'}
|
||||||
color={isConvo ? 'blue' : 'gray'}
|
color={isConvo ? 'blue' : 'gray'}
|
||||||
onClick={() => handleNavigation('convo')}
|
size={40}
|
||||||
size={48}
|
|
||||||
radius="md"
|
radius="md"
|
||||||
>
|
>
|
||||||
<IconMessageCircle size={24} />
|
<IconMessageCircle size={20} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
|
<Text size="xs" c={isConvo ? 'blue' : 'dimmed'}>Convo</Text>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack gap={4} align="center" onClick={() => handleNavigation('edit')} style={{ cursor: 'pointer' }}>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant={isEdit ? 'filled' : 'subtle'}
|
variant={isEdit ? 'filled' : 'subtle'}
|
||||||
color={isEdit ? 'blue' : 'gray'}
|
color={isEdit ? 'blue' : 'gray'}
|
||||||
onClick={() => handleNavigation('edit')}
|
size={40}
|
||||||
size={48}
|
|
||||||
radius="md"
|
radius="md"
|
||||||
>
|
>
|
||||||
<IconEdit size={24} />
|
<IconEdit size={20} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
|
<Text size="xs" c={isEdit ? 'blue' : 'dimmed'}>Manual</Text>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack gap={4} align="center" onClick={() => handleNavigation('galaxy')} style={{ cursor: 'pointer' }}>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant={isGalaxy ? 'filled' : 'subtle'}
|
variant={isGalaxy ? 'filled' : 'subtle'}
|
||||||
color={isGalaxy ? 'blue' : 'gray'}
|
color={isGalaxy ? 'blue' : 'gray'}
|
||||||
onClick={() => handleNavigation('galaxy')}
|
size={40}
|
||||||
size={48}
|
|
||||||
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>
|
||||||
|
|||||||
@@ -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">
|
||||||
|
<Box w={32} h={32} style={{ flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||||
<Image
|
<Image
|
||||||
src="/logo.svg"
|
src="/logo.svg"
|
||||||
alt="Ponderants logo"
|
alt="Ponderants logo"
|
||||||
w={56}
|
style={{ width: '100%', height: '100%', transform: 'scale(3.5)' }}
|
||||||
h={56}
|
|
||||||
style={{ flexShrink: 0 }}
|
|
||||||
/>
|
/>
|
||||||
<Text fw={700} size="xl">
|
</Box>
|
||||||
|
<Text fw={700} size="lg">
|
||||||
Ponderants
|
Ponderants
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -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 |
Reference in New Issue
Block a user