diff --git a/packages/web/src/components/chat/FriendsPage.tsx b/packages/web/src/components/chat/FriendsPage.tsx index 8e5be27e..5c022071 100644 --- a/packages/web/src/components/chat/FriendsPage.tsx +++ b/packages/web/src/components/chat/FriendsPage.tsx @@ -197,7 +197,7 @@ export function FriendsPage() { ); diff --git a/packages/web/src/components/layout/ServerSidebar.tsx b/packages/web/src/components/layout/ServerSidebar.tsx index 1a8ad02d..e51bec14 100644 --- a/packages/web/src/components/layout/ServerSidebar.tsx +++ b/packages/web/src/components/layout/ServerSidebar.tsx @@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom'; import { useServerStore } from '../../stores/serverStore'; import { useChatStore } from '../../stores/chatStore'; import { useUIStore } from '../../stores/uiStore'; -import { Tooltip } from '../ui/Tooltip'; + import { getServerGradient, HOME_GRADIENT } from '../../utils/gradients'; interface SidebarItemProps { @@ -13,7 +13,7 @@ interface SidebarItemProps { active: boolean; onClick: () => void; type?: 'server' | 'dm' | 'action'; - actionType?: 'add' | 'join' | 'explore'; + actionType?: 'add' | 'join'; hasUnread?: boolean; } @@ -22,94 +22,86 @@ function SidebarItem({ id, name, icon, active, onClick, type = 'server', actionT const firstLetter = name.charAt(0).toUpperCase(); const getPillHeight = () => { - if (active) return 'h-10'; - if (isHovered) return 'h-5'; + if (active) return 'h-8'; + if (isHovered) return 'h-4'; if (hasUnread && !active) return 'h-2'; return 'h-2 scale-0'; }; const backgroundStyle = useMemo((): React.CSSProperties | undefined => { - if (type === 'action') return undefined; + if (type === 'action') { + return { + background: isHovered ? 'rgba(134, 239, 172, 0.12)' : 'rgba(255, 255, 255, 0.04)', + }; + } if (type === 'dm') { - return { - background: HOME_GRADIENT.gradient, - ...(isHovered ? { boxShadow: `0 0 12px ${HOME_GRADIENT.glow}40` } : {}), - }; + return { background: HOME_GRADIENT.gradient }; } // Server type — if it has a custom icon image, no gradient needed if (icon) return undefined; const serverGrad = getServerGradient(id, name); - return { - background: serverGrad.gradient, - ...(isHovered ? { boxShadow: `0 0 12px ${serverGrad.glow}40` } : {}), - }; - }, [type, id, name, icon, active, isHovered]); + return { background: serverGrad.gradient }; + }, [type, id, name, icon, isHovered]); const getButtonClasses = () => { - const base = 'w-12 h-12 flex items-center justify-center transition-all duration-200 overflow-hidden relative group'; + const base = 'w-10 h-10 flex items-center justify-center duration-200 overflow-hidden [transition:border-radius_0.2s,background_0.2s,color_0.2s]'; if (type === 'dm') { - return `${base} text-white ${active ? 'rounded-[16px]' : 'rounded-[24px] hover:rounded-[16px]'}`; + return `${base} text-white ${active ? 'rounded-[13px]' : 'rounded-[20px] hover:rounded-[13px]'}`; } if (type === 'action') { - return `${base} bg-surface-chat rounded-[24px] hover:rounded-[16px] text-status-online hover:bg-status-online hover:text-white`; + return `${base} rounded-[20px] hover:rounded-[13px] text-accent-mint`; } if (icon) { - return `${base} ${active ? 'rounded-[16px]' : 'rounded-[24px] hover:rounded-[16px]'}`; + return `${base} ${active ? 'rounded-[13px]' : 'rounded-[20px] hover:rounded-[13px]'}`; } - return `${base} text-white ${active ? 'rounded-[16px]' : 'rounded-[24px] hover:rounded-[16px]'}`; + return `${base} text-white ${active ? 'rounded-[13px]' : 'rounded-[20px] hover:rounded-[13px]'}`; }; return (
setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} > {/* Pill Indicator */} {(type === 'server' || type === 'dm') && ( -
+
)} - - - + + + + ) + ) : icon ? ( + {name} + ) : ( + {firstLetter} + )} +
); } @@ -167,7 +159,7 @@ export function ServerSidebar() { hasUnread={hasDmUnread} /> -
+
{servers.map((server) => ( ))} +
+ - {}} - type="action" - actionType="explore" - /> ); } diff --git a/packages/web/src/components/ui/Avatar.tsx b/packages/web/src/components/ui/Avatar.tsx index f970419a..a28b0eec 100644 --- a/packages/web/src/components/ui/Avatar.tsx +++ b/packages/web/src/components/ui/Avatar.tsx @@ -69,12 +69,13 @@ export function Avatar({ src, name, size = 40, status, className = '', onClick,
{status && (
)} diff --git a/packages/web/src/components/ui/UserProfilePopout.tsx b/packages/web/src/components/ui/UserProfilePopout.tsx index 5c884a6e..b1cc14b9 100644 --- a/packages/web/src/components/ui/UserProfilePopout.tsx +++ b/packages/web/src/components/ui/UserProfilePopout.tsx @@ -18,6 +18,13 @@ export function UserProfilePopout({ user, onClose, position }: UserProfilePopout const addDmChannel = useServerStore((s) => s.addDmChannel); const displayName = user.displayName ?? user.username; + const top = position + ? Math.min(Math.max(8, position.top), window.innerHeight - 360) + : undefined; + const left = position + ? Math.min(Math.max(8, position.left), window.innerWidth - 316) + : undefined; + const handleSendMessage = async () => { try { const channel = await api.dm.create({ userId: user.id }); @@ -29,59 +36,77 @@ export function UserProfilePopout({ user, onClose, position }: UserProfilePopout console.error('Failed to create DM channel:', err); } }; - + return ( -
{/* Banner */} -
- - {/* Avatar Container */} -
-
+
+ + {/* Body */} +
+ {/* Avatar — negative margin pulls it into the banner while staying in flow */} +
- - {/* Content */} -
-
+ + {/* Name & info — flows naturally after avatar */} +
+
{displayName}
-
+
@{user.username}
- -
- -
-
Backspace Member Since
-
- {new Date(user.createdAt).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })} -
-
- {user.customStatus && ( -
-
Status
-
{user.customStatus}
+
+ {user.customStatus}
)}
-
- - {/* Footer / Actions */} -
-