feat: Phase 1 Aether Drift — mobile-first grid layout and token migration

Replace Discord flex layout with CSS Grid on desktop (312px sidebar +
1fr main) and overlay drawer on mobile. Migrate all discord-* class
references to Aether Drift design tokens across 15 files (~200 refs).

Layout: ServerSidebar becomes a fixed glass strip (md:glass-strip),
ChannelSidebar gets pl-[72px] offset, bottom bar is a glass-bubble
(z-105), MessageInput floats as absolute glass pill on desktop (z-110).
MemberSidebar/ActivityPanel hidden on mobile via hidden md:block.

Z-index stack: MobileNav backdrop z-35, sidebar z-40, glass strip z-100,
bottom bar z-105, input bubble z-110, hamburger z-120, profile popout
z-145, device panels z-150, modals z-200.
This commit is contained in:
Jannis Braun
2026-03-01 22:46:27 +01:00
parent e219229b63
commit 79252fe54a
15 changed files with 412 additions and 230 deletions
@@ -216,16 +216,16 @@ export function MessageInput({ channelId, channelName }: MessageInputProps) {
};
return (
<div className="px-4 pb-6 flex-shrink-0">
<div className="px-3 pb-3 flex-shrink-0 md:absolute md:bottom-3 md:left-3 md:right-3 md:z-[110] md:px-0 md:pb-0 md:glass-bubble md:rounded-[14px]">
{replyTo && (
<div className="bg-discord-bg-hover rounded-t-lg px-4 py-2 flex items-center justify-between border-b border-discord-bg-tertiary/50">
<div className="flex items-center gap-1 text-[14px] text-discord-text-normal truncate">
<div className="bg-interactive-hover rounded-t-lg px-4 py-2 flex items-center justify-between border-b border-border-hard/50">
<div className="flex items-center gap-1 text-[14px] text-txt-message truncate">
<span className="opacity-60">Replying to</span>
<span className="font-bold">{replyTo.user.displayName ?? replyTo.user.username}</span>
</div>
<button
onClick={() => setReplyTo(null)}
className="text-discord-text-muted hover:text-discord-text-primary transition-colors"
className="text-txt-tertiary hover:text-txt-primary transition-colors"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
<path d="M18.4 4L12 10.4L5.6 4L4 5.6L10.4 12L4 18.4L5.6 20L12 13.6L18.4 20L20 18.4L13.6 12L20 5.6L18.4 4Z" />
@@ -234,7 +234,7 @@ export function MessageInput({ channelId, channelName }: MessageInputProps) {
</div>
)}
<div
className={`relative bg-discord-bg-input ${replyTo ? 'rounded-b-lg' : 'rounded-lg'} overflow-visible`}
className={`relative bg-surface-input md:bg-transparent ${replyTo ? 'rounded-b-lg' : 'rounded-lg md:rounded-none'} overflow-visible`}
onDrop={handleDrop}
onDragOver={handleDragOver}
>
@@ -249,9 +249,9 @@ export function MessageInput({ channelId, channelName }: MessageInputProps) {
{/* File previews */}
{files.length > 0 && (
<div className="p-4 flex flex-wrap gap-4 bg-discord-bg-secondary/30">
<div className="p-4 flex flex-wrap gap-4 bg-surface-channel/30">
{files.map((file, i) => (
<div key={i} className="relative group bg-discord-bg-secondary rounded-lg p-2 max-w-[200px] shadow-elevation-low border border-discord-bg-tertiary">
<div key={i} className="relative group bg-surface-channel rounded-lg p-2 max-w-[200px] shadow-elevation-low border border-border-hard">
{file.type.startsWith('image/') ? (
<img
src={URL.createObjectURL(file)}
@@ -259,7 +259,7 @@ export function MessageInput({ channelId, channelName }: MessageInputProps) {
className="max-h-[150px] rounded object-cover"
/>
) : (
<div className="flex items-center gap-2 text-sm text-discord-text-secondary py-4 px-2">
<div className="flex items-center gap-2 text-sm text-txt-secondary py-4 px-2">
<svg className="w-8 h-8 opacity-60" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
@@ -268,7 +268,7 @@ export function MessageInput({ channelId, channelName }: MessageInputProps) {
)}
<button
onClick={() => removeFile(i)}
className="absolute -top-2 -right-2 w-7 h-7 bg-discord-red hover:bg-discord-red-hover shadow-elevation-high rounded-lg flex items-center justify-center text-white transition-colors z-10"
className="absolute -top-2 -right-2 w-7 h-7 bg-accent-rose hover:bg-accent-rose/80 shadow-elevation-high rounded-lg flex items-center justify-center text-white transition-colors z-10"
>
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor">
<path d="M5 2a1 1 0 011-1h4a1 1 0 011 1v1h3a1 1 0 110 2h-.08L13 14a2 2 0 01-2 2H5a2 2 0 01-2-2L2.08 5H2a1 1 0 110-2h3V2zm2 0v1h2V2H7z" />
@@ -283,7 +283,7 @@ export function MessageInput({ channelId, channelName }: MessageInputProps) {
{/* File attach button */}
<button
onClick={() => fileInputRef.current?.click()}
className="p-3 text-discord-text-muted hover:text-discord-text-secondary transition-colors sticky top-0"
className="p-3 text-txt-tertiary hover:text-txt-secondary transition-colors sticky top-0"
title="Attach file"
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
@@ -312,14 +312,14 @@ export function MessageInput({ channelId, channelName }: MessageInputProps) {
onKeyDown={handleKeyDown}
onPaste={handlePaste}
placeholder={`Message ${channelName.startsWith('@') ? channelName : `#${channelName}`}`}
className="flex-1 py-[11px] px-1 bg-transparent text-discord-text-primary placeholder-discord-text-muted/60 outline-none resize-none text-[15px] leading-[1.375rem] max-h-[50vh] scrollbar-thin"
className="flex-1 py-[11px] px-1 bg-transparent text-txt-primary placeholder-txt-tertiary/60 outline-none resize-none text-[15px] leading-[1.375rem] max-h-[50vh] scrollbar-thin"
rows={1}
disabled={isUploading}
/>
{/* Send indicator */}
{isUploading && (
<div className="p-3 text-discord-text-muted">
<div className="p-3 text-txt-tertiary">
<svg className="w-5 h-5 animate-spin" viewBox="0 0 24 24" fill="none">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
@@ -328,21 +328,21 @@ export function MessageInput({ channelId, channelName }: MessageInputProps) {
)}
{/* GIF button */}
<button className="p-2 text-discord-text-muted hover:text-discord-text-secondary transition-colors" title="GIF">
<button className="p-2 text-txt-tertiary hover:text-txt-secondary transition-colors" title="GIF">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path d="M2 5.5A2.5 2.5 0 0 1 4.5 3h15A2.5 2.5 0 0 1 22 5.5v13a2.5 2.5 0 0 1-2.5 2.5h-15A2.5 2.5 0 0 1 2 18.5v-13ZM5.1 14V10h3.2v1.2H6.5v.6h1.6v1.1H6.5V14H5.1Zm4.5 0V10h1.4v4H9.6Zm2.5 0V10h3.2v1.2h-1.8v.5h1.6v1h-1.6V14h-1.4Z" />
</svg>
</button>
{/* Sticker button */}
<button className="p-2 text-discord-text-muted hover:text-discord-text-secondary transition-colors" title="Stickers">
<button className="p-2 text-txt-tertiary hover:text-txt-secondary transition-colors" title="Stickers">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path d="M12.5 2C6.81 2 2 6.81 2 12.5S6.81 23 12.5 23c1.31 0 2.56-.25 3.73-.7l5.07-5.07c.45-1.17.7-2.42.7-3.73C22 7.81 17.19 2 12.5 2Zm0 19c-4.69 0-8.5-3.81-8.5-8.5S7.81 4 12.5 4 21 7.81 21 12.5c0 .89-.14 1.74-.4 2.54l-3.56 3.56c-.8.26-1.65.4-2.54.4ZM8 11.5c.83 0 1.5-.67 1.5-1.5S8.83 8.5 8 8.5 6.5 9.17 6.5 10s.67 1.5 1.5 1.5Zm6 0c.83 0 1.5-.67 1.5-1.5s-.67-1.5-1.5-1.5-1.5.67-1.5 1.5.67 1.5 1.5 1.5Zm-1 3.5c-2.33 0-4.31-1.46-5.11-3.5h10.22c-.8 2.04-2.78 3.5-5.11 3.5Z" />
</svg>
</button>
{/* Emoji button */}
<button className="p-2 text-discord-text-muted hover:text-discord-text-secondary transition-colors" title="Emoji">
<button className="p-2 text-txt-tertiary hover:text-txt-secondary transition-colors" title="Emoji">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm3.5-9c.83 0 1.5-.67 1.5-1.5S16.33 8 15.5 8 14 8.67 14 9.5s.67 1.5 1.5 1.5zm-7 0c.83 0 1.5-.67 1.5-1.5S9.33 8 8.5 8 7 8.67 7 9.5s.67 1.5 1.5 1.5zm3.5 6.5c2.33 0 4.31-1.46 5.11-3.5H6.89c.8 2.04 2.78 3.5 5.11 3.5z" />
</svg>
@@ -124,7 +124,7 @@ export function MessageList({ channelId }: MessageListProps) {
{!hasMore && <WelcomeHeader channelId={channelId} />}
<div className="pb-6">
<div className="pb-6 md:pb-[88px]">
{messages.map((msg, i) => {
const prevMsg = messages[i - 1];
const showDate = shouldShowDateDivider(prevMsg, msg);
@@ -134,11 +134,11 @@ export function MessageList({ channelId }: MessageListProps) {
<React.Fragment key={msg.id}>
{showDate && (
<div className="flex items-center px-4 my-6 select-none pointer-events-none">
<div className="flex-1 h-[1px] bg-discord-modifier-accent" />
<span className="px-2 text-[12px] font-bold text-discord-text-muted leading-tight">
<div className="flex-1 h-[1px] bg-interactive-muted" />
<span className="px-2 text-[12px] font-bold text-txt-tertiary leading-tight">
{formatDateDivider(msg.createdAt)}
</span>
<div className="flex-1 h-[1px] bg-discord-modifier-accent" />
<div className="flex-1 h-[1px] bg-interactive-muted" />
</div>
)}
<Message
@@ -175,35 +175,35 @@ function WelcomeHeader({ channelId }: { channelId: string }) {
<div className="mb-2">
<Avatar src={otherUser?.avatar} name={displayName} size={80} />
</div>
<h3 className="text-[32px] leading-10 font-bold text-discord-text-primary">{displayName}</h3>
<p className="text-discord-text-secondary text-[14px] mt-1">
<h3 className="text-[32px] leading-10 font-bold text-txt-primary">{displayName}</h3>
<p className="text-txt-secondary text-[14px] mt-1">
This is the beginning of your direct message history with <strong>@{username}</strong>.
</p>
{isFriend && otherUser && (
<div className="mt-4">
<button
onClick={() => removeFriend(otherUser.id)}
className="px-4 py-1.5 bg-discord-bg-accent hover:bg-discord-bg-surface-higher text-[14px] font-medium text-discord-text-primary rounded-[3px] transition-colors"
className="px-4 py-1.5 bg-surface-elevated hover:bg-surface-elevated text-[14px] font-medium text-txt-primary rounded-[3px] transition-colors"
>
Remove Friend
</button>
</div>
)}
<div className="mt-6 border-b border-discord-modifier-accent" />
<div className="mt-6 border-b border-interactive-muted" />
</div>
);
}
return (
<div className="px-4 pt-8 pb-4">
<div className="w-[68px] h-[68px] rounded-full bg-discord-bg-accent flex items-center justify-center mb-4 text-white">
<div className="w-[68px] h-[68px] rounded-full bg-surface-elevated flex items-center justify-center mb-4 text-white">
<svg width="42" height="42" viewBox="0 0 24 24" fill="currentColor">
<path d="M5.88657 21C5.57547 21 5.3399 20.7189 5.39427 20.4126L6.00001 17H2.59511C2.28449 17 2.04905 16.7198 2.10259 16.4138L2.27759 15.4138C2.31946 15.1746 2.52722 15 2.77011 15H6.35001L7.41001 9H4.00511C3.69449 9 3.45905 8.71977 3.51259 8.41381L3.68759 7.41381C3.72946 7.17456 3.93722 7 4.18011 7H7.76001L8.39677 3.41262C8.43914 3.17391 8.64664 3 8.88907 3H9.87344C10.1845 3 10.4201 3.28107 10.3657 3.58738L9.76001 7H15.76L16.3968 3.41262C16.4391 3.17391 16.6466 3 16.8891 3H17.8734C18.1845 3 18.4201 3.28107 18.3657 3.58738L17.76 7H21.1649C21.4755 7 21.711 7.28023 21.6574 7.58619L21.4824 8.58619C21.4406 8.82544 21.2328 9 20.9899 9H17.41L16.35 15H19.7549C20.0655 15 20.301 15.2802 20.2474 15.5862L20.0724 16.5862C20.0306 16.8254 19.8228 17 19.5799 17H16L15.3632 20.5874C15.3209 20.8261 15.1134 21 14.8709 21H13.8866C13.5755 21 13.3399 20.7189 13.3943 20.4126L14 17H8.00001L7.36325 20.5874C7.32088 20.8261 7.11337 21 6.87094 21H5.88657ZM9.41001 9L8.35001 15H14.35L15.41 9H9.41001Z" />
</svg>
</div>
<h3 className="text-[32px] leading-10 font-bold text-discord-text-primary">Welcome to the channel!</h3>
<p className="text-discord-text-secondary text-[16px] mt-2">This is the start of the conversation.</p>
<div className="mt-6 border-b border-discord-modifier-accent" />
<h3 className="text-[32px] leading-10 font-bold text-txt-primary">Welcome to the channel!</h3>
<p className="text-txt-secondary text-[16px] mt-2">This is the start of the conversation.</p>
<div className="mt-6 border-b border-interactive-muted" />
</div>
);
}
@@ -46,7 +46,7 @@ export function ActivityPanel() {
<div
key={friend.id}
onClick={(e) => handleFriendClick(e, friend)}
className="flex items-center gap-3 px-2 py-1.5 rounded-[4px] hover:bg-discord-modifier-hover cursor-pointer group transition-colors"
className="flex items-center gap-3 px-2 py-1.5 rounded-[4px] hover:bg-interactive-hover cursor-pointer group transition-colors"
>
<Avatar
src={friend.avatar}
@@ -56,25 +56,25 @@ export function ActivityPanel() {
className={isOffline ? 'opacity-60' : undefined}
/>
<div className="flex-1 min-w-0">
<div className={`text-[15px] font-medium truncate ${isOffline ? 'text-discord-text-muted' : 'text-discord-text-primary'}`}>
<div className={`text-[15px] font-medium truncate ${isOffline ? 'text-txt-tertiary' : 'text-txt-primary'}`}>
{friend.displayName ?? friend.username}
</div>
{!isOffline && friend.customStatus && (
<div className="text-[12px] text-discord-text-muted truncate">{friend.customStatus}</div>
<div className="text-[12px] text-txt-tertiary truncate">{friend.customStatus}</div>
)}
</div>
</div>
);
return (
<div className="w-60 bg-discord-bg-secondary flex-shrink-0 overflow-y-auto select-none no-scrollbar">
<div className="w-60 bg-surface-channel flex-shrink-0 overflow-y-auto select-none no-scrollbar hidden md:block">
<div className="p-3">
<h3 className="text-[20px] font-bold text-discord-text-header mb-4 px-2">Active Now</h3>
<h3 className="text-[20px] font-bold text-txt-primary mb-4 px-2">Active Now</h3>
{onlineFriends.length === 0 && offlineFriends.length === 0 ? (
<div className="text-center py-8">
<div className="text-[16px] font-bold text-discord-text-header mb-1">It's quiet for now...</div>
<div className="text-[14px] text-discord-text-muted max-w-[200px] mx-auto">
<div className="text-[16px] font-bold text-txt-primary mb-1">It's quiet for now...</div>
<div className="text-[14px] text-txt-tertiary max-w-[200px] mx-auto">
When a friend starts an activity&#8212;like playing a game or hanging out on voice&#8212;we'll show it here!
</div>
</div>
@@ -82,7 +82,7 @@ export function ActivityPanel() {
<>
{onlineFriends.length > 0 && (
<div className="mb-4">
<h3 className="text-[12px] font-bold text-discord-text-muted uppercase tracking-wider px-2 mb-1">
<h3 className="text-[12px] font-bold text-txt-tertiary uppercase tracking-wider px-2 mb-1">
ONLINE {onlineFriends.length}
</h3>
{onlineFriends.map(f => renderFriend(f))}
@@ -90,7 +90,7 @@ export function ActivityPanel() {
)}
{offlineFriends.length > 0 && (
<div>
<h3 className="text-[12px] font-bold text-discord-text-muted uppercase tracking-wider px-2 mb-1">
<h3 className="text-[12px] font-bold text-txt-tertiary uppercase tracking-wider px-2 mb-1">
OFFLINE {offlineFriends.length}
</h3>
{offlineFriends.map(f => renderFriend(f, true))}
@@ -204,28 +204,28 @@ export function AppLayout() {
if (isLoading || !user) {
return (
<div className="h-screen flex items-center justify-center bg-discord-bg-primary">
<div className="h-screen flex items-center justify-center bg-surface-chat">
<div className="text-center">
<svg className="animate-spin w-10 h-10 text-discord-blurple mx-auto mb-4" viewBox="0 0 24 24" fill="none">
<svg className="animate-spin w-10 h-10 text-accent-primary mx-auto mb-4" viewBox="0 0 24 24" fill="none">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
</svg>
<p className="text-discord-text-muted">Loading Backspace...</p>
<p className="text-txt-tertiary">Loading Backspace...</p>
</div>
</div>
);
}
return (
<div className="h-screen flex bg-discord-bg-tertiary overflow-hidden">
<div className="h-screen flex flex-col md:grid md:grid-cols-[312px_1fr] bg-surface-base overflow-hidden">
{/* Server sidebar - always visible on desktop, toggled on mobile */}
<div className={`${isMobile ? `fixed z-40 h-full transition-transform ${sidebarOpen ? 'translate-x-0' : '-translate-x-full'}` : 'flex h-full'}`}>
<div className={`fixed inset-y-0 left-0 z-40 flex w-[312px] transition-transform duration-200 ${sidebarOpen ? 'translate-x-0' : '-translate-x-full md:translate-x-0'} md:static md:z-auto md:w-auto md:transform-none`}>
<ServerSidebar />
<ChannelSidebar />
</div>
{/* Main content area */}
<div className="flex-1 flex min-w-0 bg-discord-bg-primary relative">
<div className="flex-1 flex min-w-0 bg-surface-chat relative">
<MainContent />
<RightPanel />
</div>
@@ -250,7 +250,7 @@ export function AppLayout() {
{userProfilePopout.user && userProfilePopout.position && (
<>
<div
className="fixed inset-0 z-[45]"
className="fixed inset-0 z-[145]"
onClick={closeUserProfile}
/>
<UserProfilePopout
@@ -97,8 +97,8 @@ export function ChannelSidebar() {
// Floating bottom panel — shared between DM view and server view
const floatingPanel = user ? (
<div className="fixed bottom-2 left-2 z-30 w-[296px]">
<div className="bg-[#1a1b1e] rounded-lg shadow-[0_4px_20px_rgba(0,0,0,0.5)] ring-1 ring-white/[0.06]">
<div className="fixed bottom-0 left-0 right-0 z-[105] p-2 md:right-auto md:w-[296px] md:bottom-[10px] md:left-[10px] md:p-0">
<div className="glass-bubble rounded-[14px]">
{/* Voice controls (expands when connected) */}
{(currentVoiceChannelId || activeDmCall) && <VoiceControls />}
{/* Separator between voice and user area */}
@@ -119,9 +119,9 @@ export function ChannelSidebar() {
if (!server) {
return (
<>
<div className="w-60 bg-discord-bg-secondary flex flex-col flex-shrink-0 select-none">
<div className="w-60 md:w-full bg-surface-channel flex flex-col flex-shrink-0 select-none md:pl-[72px]">
<div className="h-12 px-[10px] flex items-center shadow-header z-10">
<button className="flex-1 bg-discord-bg-tertiary text-discord-text-muted text-[13px] font-medium py-[5px] px-2 rounded-[4px] text-left hover:bg-discord-bg-tertiary/80 transition-colors">
<button className="flex-1 bg-surface-base text-txt-tertiary text-[13px] font-medium py-[5px] px-2 rounded-[4px] text-left hover:bg-surface-base/80 transition-colors">
Find or start a conversation
</button>
</div>
@@ -130,8 +130,8 @@ export function ChannelSidebar() {
onClick={handleHomeClick}
className={`flex items-center gap-3 px-2 h-[42px] rounded-[4px] cursor-pointer mb-[2px] transition-colors group ${
!currentChannelId
? 'bg-discord-modifier-selected text-white'
: 'text-discord-text-muted hover:bg-discord-modifier-hover hover:text-discord-text-secondary'
? 'bg-interactive-selected text-white'
: 'text-txt-tertiary hover:bg-interactive-hover hover:text-txt-secondary'
}`}
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" className={`flex-shrink-0 ${!currentChannelId ? 'text-white' : 'opacity-70 group-hover:opacity-100'}`}>
@@ -144,7 +144,7 @@ export function ChannelSidebar() {
{/* Nitro */}
<div
className="flex items-center gap-3 px-2 h-[42px] rounded-[4px] cursor-pointer mb-[2px] transition-colors group text-discord-text-muted hover:bg-discord-modifier-hover hover:text-discord-text-secondary"
className="flex items-center gap-3 px-2 h-[42px] rounded-[4px] cursor-pointer mb-[2px] transition-colors group text-txt-tertiary hover:bg-interactive-hover hover:text-txt-secondary"
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" className="flex-shrink-0 opacity-70 group-hover:opacity-100">
<path d="M2.98966977,9.35789159 C2.98966977,9.77582472 2.63542482,10.1300697 2.21749169,10.1300697 L2.21749169,10.1300697 C1.79955856,10.1300697 1.44531361,9.77582472 1.44531361,9.35789159 L1.44531361,9.35789159 C1.44531361,8.93995846 1.79955856,8.58571351 2.21749169,8.58571351 L2.21749169,8.58571351 C2.63542482,8.58571351 2.98966977,8.93995846 2.98966977,9.35789159 Z" transform="translate(12, 12) scale(1.2) translate(-12, -12)" />
@@ -155,7 +155,7 @@ export function ChannelSidebar() {
{/* Shop */}
<div
className="flex items-center gap-3 px-2 h-[42px] rounded-[4px] cursor-pointer mb-[2px] transition-colors group text-discord-text-muted hover:bg-discord-modifier-hover hover:text-discord-text-secondary"
className="flex items-center gap-3 px-2 h-[42px] rounded-[4px] cursor-pointer mb-[2px] transition-colors group text-txt-tertiary hover:bg-interactive-hover hover:text-txt-secondary"
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" className="flex-shrink-0 opacity-70 group-hover:opacity-100">
<path d="M2 5.5A1.5 1.5 0 0 1 3.5 4h17A1.5 1.5 0 0 1 22 5.5V7H2V5.5ZM2 9v9.5A1.5 1.5 0 0 0 3.5 20h17a1.5 1.5 0 0 0 1.5-1.5V9H2Zm9.5 3a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5Zm-4-1h2a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-1a1 1 0 0 1 1-1Z" />
@@ -164,10 +164,10 @@ export function ChannelSidebar() {
</div>
<div className="mt-[18px] px-2 mb-1 flex items-center justify-between group">
<span className="text-[12px] font-bold text-discord-text-muted uppercase tracking-wider">Direct Messages</span>
<span className="text-[12px] font-bold text-txt-tertiary uppercase tracking-wider">Direct Messages</span>
<button
onClick={() => openModal('newDm')}
className="text-discord-text-muted hover:text-discord-text-primary transition-colors"
className="text-txt-tertiary hover:text-txt-primary transition-colors"
title="New Direct Message"
>
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
@@ -193,10 +193,10 @@ export function ChannelSidebar() {
onClick={() => handleChannelClick(dm.id)}
className={`relative flex items-center gap-3 px-2 h-[42px] rounded-[4px] cursor-pointer transition-colors group ${
currentChannelId === dm.id
? 'bg-discord-modifier-selected text-white'
? 'bg-interactive-selected text-white'
: isDmUnread
? 'text-white hover:bg-discord-modifier-hover'
: 'text-discord-text-muted hover:bg-discord-modifier-hover hover:text-discord-text-secondary'
? 'text-white hover:bg-interactive-hover'
: 'text-txt-tertiary hover:bg-interactive-hover hover:text-txt-secondary'
}`}
>
{isDmUnread && (
@@ -207,7 +207,7 @@ export function ChannelSidebar() {
{otherMembers.slice(0, 2).map((m, i) => (
<div
key={m.id}
className="absolute rounded-full overflow-hidden border-2 border-discord-bg-secondary"
className="absolute rounded-full overflow-hidden border-2 border-surface-channel"
style={{
width: 22, height: 22,
left: i * 10,
@@ -226,16 +226,16 @@ export function ChannelSidebar() {
<div className={`text-[15px] truncate leading-tight ${
currentChannelId === dm.id ? 'text-white font-medium'
: isDmUnread ? 'text-white font-bold'
: 'text-discord-text-muted group-hover:text-discord-text-secondary font-medium'
: 'text-txt-tertiary group-hover:text-txt-secondary font-medium'
}`}>
{dmDisplayName}
</div>
{isGroup ? (
<div className="text-[12px] text-discord-channels-default truncate leading-tight mt-0.5">
<div className="text-[12px] text-txt-tertiary truncate leading-tight mt-0.5">
{dm.members.length} Members
</div>
) : dm.lastMessage ? (
<div className="text-[12px] text-discord-channels-default truncate leading-tight mt-0.5">
<div className="text-[12px] text-txt-tertiary truncate leading-tight mt-0.5">
{dm.lastMessage.content}
</div>
) : null}
@@ -250,7 +250,7 @@ export function ChannelSidebar() {
}
useServerStore.getState().closeDm(dm.id);
}}
className="opacity-0 group-hover:opacity-100 text-discord-text-muted hover:text-discord-text-primary transition-opacity flex-shrink-0 ml-1"
className="opacity-0 group-hover:opacity-100 text-txt-tertiary hover:text-txt-primary transition-opacity flex-shrink-0 ml-1"
title="Close DM"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
@@ -261,7 +261,7 @@ export function ChannelSidebar() {
);
})}
{dmChannels.length === 0 && (
<p className="px-2 py-4 text-[13px] text-discord-text-muted italic opacity-60">No DM conversations yet.</p>
<p className="px-2 py-4 text-[13px] text-txt-tertiary italic opacity-60">No DM conversations yet.</p>
)}
</div>
</div>
@@ -274,21 +274,21 @@ export function ChannelSidebar() {
return (
<>
<div className="w-60 bg-discord-bg-secondary flex flex-col flex-shrink-0 select-none">
<div className="w-60 md:w-full bg-surface-channel flex flex-col flex-shrink-0 select-none md:pl-[72px]">
{/* Server header */}
<div className="h-12 flex items-center shadow-header z-10 group/header">
<button
onClick={() => openModal('serverSettings')}
className="flex-1 h-full px-4 flex items-center justify-between hover:bg-discord-modifier-hover transition-colors min-w-0"
className="flex-1 h-full px-4 flex items-center justify-between hover:bg-interactive-hover transition-colors min-w-0"
>
<span className="font-bold text-[16px] text-discord-text-primary truncate leading-tight">{server.name}</span>
<svg width="18" height="18" viewBox="0 0 18 18" fill="currentColor" className="text-discord-text-muted flex-shrink-0">
<span className="font-bold text-[16px] text-txt-primary truncate leading-tight">{server.name}</span>
<svg width="18" height="18" viewBox="0 0 18 18" fill="currentColor" className="text-txt-tertiary flex-shrink-0">
<path d="M5.293 7.293a1 1 0 011.414 0L9 9.586l2.293-2.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" />
</svg>
</button>
<button
onClick={() => openModal('invite')}
className="w-10 h-full flex items-center justify-center text-discord-text-muted hover:text-discord-text-primary hover:bg-discord-modifier-hover transition-all flex-shrink-0"
className="w-10 h-full flex items-center justify-center text-txt-tertiary hover:text-txt-primary hover:bg-interactive-hover transition-all flex-shrink-0"
title="Invite People"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
@@ -302,7 +302,7 @@ export function ChannelSidebar() {
{/* Text Channels */}
<div>
<div className="flex items-center justify-between px-1 mb-1 group cursor-pointer">
<div className="flex items-center gap-0.5 text-discord-text-muted hover:text-discord-text-secondary transition-colors">
<div className="flex items-center gap-0.5 text-txt-tertiary hover:text-txt-secondary transition-colors">
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor" className="opacity-70">
<path d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z" />
</svg>
@@ -314,7 +314,7 @@ export function ChannelSidebar() {
e.stopPropagation();
openModal('createChannel');
}}
className="text-discord-text-muted hover:text-discord-text-primary transition-colors"
className="text-txt-tertiary hover:text-txt-primary transition-colors"
title="Create Channel"
>
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
@@ -332,10 +332,10 @@ export function ChannelSidebar() {
onClick={() => handleChannelClick(channel.id)}
className={`w-full flex items-center gap-1.5 px-2 h-8 rounded-[4px] group transition-colors ${
currentChannelId === channel.id
? 'bg-discord-modifier-selected text-white'
? 'bg-interactive-selected text-white'
: isUnread
? 'text-white hover:text-white hover:bg-discord-modifier-hover'
: 'text-discord-text-muted hover:text-discord-text-secondary hover:bg-discord-modifier-hover'
? 'text-white hover:text-white hover:bg-interactive-hover'
: 'text-txt-tertiary hover:text-txt-secondary hover:bg-interactive-hover'
}`}
>
{isUnread && (
@@ -351,7 +351,7 @@ export function ChannelSidebar() {
height="16"
viewBox="0 0 24 24"
fill="currentColor"
className="flex-shrink-0 opacity-0 group-hover:opacity-100 text-discord-text-muted hover:text-discord-text-primary transition-opacity"
className="flex-shrink-0 opacity-0 group-hover:opacity-100 text-txt-tertiary hover:text-txt-primary transition-opacity"
onClick={(e) => {
e.stopPropagation();
openModal('channelSettings', { channelId: channel.id });
@@ -369,7 +369,7 @@ export function ChannelSidebar() {
{/* Voice Channels */}
<div>
<div className="flex items-center justify-between px-1 mb-1 group cursor-pointer">
<div className="flex items-center gap-0.5 text-discord-text-muted hover:text-discord-text-secondary transition-colors">
<div className="flex items-center gap-0.5 text-txt-tertiary hover:text-txt-secondary transition-colors">
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor" className="opacity-70">
<path d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z" />
</svg>
@@ -381,7 +381,7 @@ export function ChannelSidebar() {
e.stopPropagation();
openModal('createChannel');
}}
className="text-discord-text-muted hover:text-discord-text-primary transition-colors"
className="text-txt-tertiary hover:text-txt-primary transition-colors"
title="Create Channel"
>
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
@@ -562,33 +562,33 @@ function UserAreaPanel({
<div className="relative" ref={panelRef}>
{/* Input settings panel */}
{openPanel === 'input' && (
<div className="absolute bottom-full left-0 right-0 mb-0 bg-[#1e1f22] rounded-t-lg shadow-lg z-50 border-t border-x border-discord-bg-tertiary">
<div className="absolute bottom-full left-0 right-0 mb-0 bg-surface-channel rounded-t-lg shadow-lg z-[150] border-t border-x border-border-hard">
{/* Input Device */}
<div className="relative">
<button
onClick={() => setShowInputDeviceList(!showInputDeviceList)}
className="w-full px-4 py-3 flex items-center justify-between hover:bg-discord-modifier-hover transition-colors"
className="w-full px-4 py-3 flex items-center justify-between hover:bg-interactive-hover transition-colors"
>
<div className="min-w-0 flex-1">
<div className="text-[15px] font-semibold text-discord-text-primary text-left">Input Device</div>
<div className="text-[13px] text-discord-text-muted truncate text-left">{selectedInputLabel}</div>
<div className="text-[15px] font-semibold text-txt-primary text-left">Input Device</div>
<div className="text-[13px] text-txt-tertiary truncate text-left">{selectedInputLabel}</div>
</div>
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" className="text-discord-text-muted flex-shrink-0 ml-2">
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" className="text-txt-tertiary flex-shrink-0 ml-2">
<path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" />
</svg>
</button>
{showInputDeviceList && (
<div className="bg-discord-bg-floating rounded-lg shadow-lg mx-2 mb-2 py-1 border border-discord-bg-tertiary">
<div className="bg-surface-base rounded-lg shadow-lg mx-2 mb-2 py-1 border border-border-hard">
{inputDevices.map(d => (
<button
key={d.deviceId}
onClick={() => selectInput(d)}
className={`w-full px-3 py-2 text-left text-[13px] hover:bg-discord-modifier-hover transition-colors flex items-center gap-2 ${
inputDeviceId === d.deviceId ? 'text-discord-text-primary' : 'text-discord-text-secondary'
className={`w-full px-3 py-2 text-left text-[13px] hover:bg-interactive-hover transition-colors flex items-center gap-2 ${
inputDeviceId === d.deviceId ? 'text-txt-primary' : 'text-txt-secondary'
}`}
>
{inputDeviceId === d.deviceId && (
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" className="text-discord-blurple flex-shrink-0">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" className="text-accent-primary flex-shrink-0">
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" />
</svg>
)}
@@ -599,11 +599,11 @@ function UserAreaPanel({
)}
</div>
<div className="mx-4 border-t border-[#2b2d31]" />
<div className="mx-4 border-t border-border-soft" />
{/* Input Volume */}
<div className="px-4 py-3">
<div className="text-[15px] font-semibold text-discord-text-primary mb-2">Input Volume</div>
<div className="text-[15px] font-semibold text-txt-primary mb-2">Input Volume</div>
<input
type="range"
min={0}
@@ -613,9 +613,9 @@ function UserAreaPanel({
const vol = Number(e.target.value);
storeSetInputVolume(vol);
}}
className="w-full h-1.5 rounded-full appearance-none cursor-pointer accent-discord-blurple bg-discord-bg-tertiary [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:shadow-md"
className="w-full h-1.5 rounded-full appearance-none cursor-pointer accent-accent-primary bg-surface-base [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:shadow-md"
style={{
background: `linear-gradient(to right, #5865f2 0%, #5865f2 ${inputVolume / 2}%, #4e5058 ${inputVolume / 2}%, #4e5058 100%)`,
background: `linear-gradient(to right, var(--accent-primary) 0%, var(--accent-primary) ${inputVolume / 2}%, var(--interactive-muted) ${inputVolume / 2}%, var(--interactive-muted) 100%)`,
}}
/>
{/* Mic level meter */}
@@ -624,22 +624,22 @@ function UserAreaPanel({
<div
key={i}
className={`flex-1 h-[6px] rounded-[1px] transition-colors duration-75 ${
i < activeBars ? 'bg-discord-text-muted' : 'bg-[#313338]'
i < activeBars ? 'bg-txt-tertiary' : 'bg-interactive-muted'
}`}
/>
))}
</div>
</div>
<div className="mx-4 border-t border-[#2b2d31]" />
<div className="mx-4 border-t border-border-soft" />
{/* Voice Settings link */}
<button
onClick={onSettingsClick}
className="w-full px-4 py-3 flex items-center justify-between hover:bg-discord-modifier-hover transition-colors"
className="w-full px-4 py-3 flex items-center justify-between hover:bg-interactive-hover transition-colors"
>
<span className="text-[15px] font-semibold text-discord-text-primary">Voice Settings</span>
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" className="text-discord-text-muted">
<span className="text-[15px] font-semibold text-txt-primary">Voice Settings</span>
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" className="text-txt-tertiary">
<path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z" />
</svg>
</button>
@@ -648,33 +648,33 @@ function UserAreaPanel({
{/* Output settings panel */}
{openPanel === 'output' && (
<div className="absolute bottom-full left-0 right-0 mb-0 bg-[#1e1f22] rounded-t-lg shadow-lg z-50 border-t border-x border-discord-bg-tertiary">
<div className="absolute bottom-full left-0 right-0 mb-0 bg-surface-channel rounded-t-lg shadow-lg z-[150] border-t border-x border-border-hard">
{/* Output Device */}
<div className="relative">
<button
onClick={() => setShowOutputDeviceList(!showOutputDeviceList)}
className="w-full px-4 py-3 flex items-center justify-between hover:bg-discord-modifier-hover transition-colors"
className="w-full px-4 py-3 flex items-center justify-between hover:bg-interactive-hover transition-colors"
>
<div className="min-w-0 flex-1">
<div className="text-[15px] font-semibold text-discord-text-primary text-left">Output Device</div>
<div className="text-[13px] text-discord-text-muted truncate text-left">{selectedOutputLabel}</div>
<div className="text-[15px] font-semibold text-txt-primary text-left">Output Device</div>
<div className="text-[13px] text-txt-tertiary truncate text-left">{selectedOutputLabel}</div>
</div>
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" className="text-discord-text-muted flex-shrink-0 ml-2">
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" className="text-txt-tertiary flex-shrink-0 ml-2">
<path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" />
</svg>
</button>
{showOutputDeviceList && (
<div className="bg-discord-bg-floating rounded-lg shadow-lg mx-2 mb-2 py-1 border border-discord-bg-tertiary">
<div className="bg-surface-base rounded-lg shadow-lg mx-2 mb-2 py-1 border border-border-hard">
{outputDevices.map(d => (
<button
key={d.deviceId}
onClick={() => selectOutput(d)}
className={`w-full px-3 py-2 text-left text-[13px] hover:bg-discord-modifier-hover transition-colors flex items-center gap-2 ${
outputDeviceId === d.deviceId ? 'text-discord-text-primary' : 'text-discord-text-secondary'
className={`w-full px-3 py-2 text-left text-[13px] hover:bg-interactive-hover transition-colors flex items-center gap-2 ${
outputDeviceId === d.deviceId ? 'text-txt-primary' : 'text-txt-secondary'
}`}
>
{outputDeviceId === d.deviceId && (
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" className="text-discord-blurple flex-shrink-0">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" className="text-accent-primary flex-shrink-0">
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" />
</svg>
)}
@@ -685,11 +685,11 @@ function UserAreaPanel({
)}
</div>
<div className="mx-4 border-t border-[#2b2d31]" />
<div className="mx-4 border-t border-border-soft" />
{/* Output Volume */}
<div className="px-4 py-3">
<div className="text-[15px] font-semibold text-discord-text-primary mb-2">Output Volume</div>
<div className="text-[15px] font-semibold text-txt-primary mb-2">Output Volume</div>
<input
type="range"
min={0}
@@ -699,21 +699,21 @@ function UserAreaPanel({
const vol = Number(e.target.value);
storeSetOutputVolume(vol);
}}
className="w-full h-1.5 rounded-full appearance-none cursor-pointer bg-discord-bg-tertiary [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:shadow-md"
className="w-full h-1.5 rounded-full appearance-none cursor-pointer bg-surface-base [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:shadow-md"
style={{
background: `linear-gradient(to right, #5865f2 0%, #5865f2 ${outputVolume / 2}%, #4e5058 ${outputVolume / 2}%, #4e5058 100%)`,
background: `linear-gradient(to right, var(--accent-primary) 0%, var(--accent-primary) ${outputVolume / 2}%, var(--interactive-muted) ${outputVolume / 2}%, var(--interactive-muted) 100%)`,
}}
/>
</div>
<div className="mx-4 border-t border-[#2b2d31]" />
<div className="mx-4 border-t border-border-soft" />
{/* Voice Settings link */}
<button
onClick={onSettingsClick}
className="w-full px-4 py-3 flex items-center justify-between hover:bg-discord-modifier-hover transition-colors"
className="w-full px-4 py-3 flex items-center justify-between hover:bg-interactive-hover transition-colors"
>
<span className="text-[15px] font-semibold text-discord-text-primary">Voice Settings</span>
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" className="text-discord-text-muted">
<span className="text-[15px] font-semibold text-txt-primary">Voice Settings</span>
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" className="text-txt-tertiary">
<path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z" />
</svg>
</button>
@@ -723,11 +723,11 @@ function UserAreaPanel({
{/* User area bar */}
<div className="h-[52px] px-2 flex items-center select-none">
{/* Avatar + name */}
<div className="p-1 hover:bg-discord-modifier-hover rounded-[4px] flex items-center gap-2 flex-1 min-w-0 cursor-pointer transition-colors group">
<div className="p-1 hover:bg-interactive-hover rounded-[4px] flex items-center gap-2 flex-1 min-w-0 cursor-pointer transition-colors group">
<Avatar src={user.avatar} name={user.displayName ?? user.username} size={32} status={user.status as any} user={user} />
<div className="flex-1 min-w-0">
<div className="text-[13px] font-semibold text-discord-text-primary truncate leading-tight">{user.displayName ?? user.username}</div>
<div className="text-[11px] text-discord-text-muted truncate leading-tight group-hover:text-discord-text-secondary">@{user.username}</div>
<div className="text-[13px] font-semibold text-txt-primary truncate leading-tight">{user.displayName ?? user.username}</div>
<div className="text-[11px] text-txt-tertiary truncate leading-tight group-hover:text-txt-secondary">@{user.username}</div>
</div>
</div>
@@ -736,8 +736,8 @@ function UserAreaPanel({
{/* Mic */}
<button
onClick={onMicToggle}
className={`w-8 h-8 flex items-center justify-center hover:bg-discord-modifier-hover rounded-l-[4px] transition-colors ${
isMuted ? 'text-discord-red' : 'text-discord-text-muted hover:text-discord-text-primary'
className={`w-8 h-8 flex items-center justify-center hover:bg-interactive-hover rounded-l-[4px] transition-colors ${
isMuted ? 'text-txt-danger' : 'text-txt-tertiary hover:text-txt-primary'
}`}
title={isMuted ? 'Unmute' : 'Mute'}
>
@@ -750,8 +750,8 @@ function UserAreaPanel({
{/* Input chevron */}
<button
onClick={() => togglePanel('input')}
className={`w-[18px] h-8 flex items-center justify-center hover:bg-discord-modifier-hover rounded-r-[4px] transition-colors ${
openPanel === 'input' ? 'text-discord-text-primary bg-discord-modifier-hover' : 'text-discord-text-muted hover:text-discord-text-primary'
className={`w-[18px] h-8 flex items-center justify-center hover:bg-interactive-hover rounded-r-[4px] transition-colors ${
openPanel === 'input' ? 'text-txt-primary bg-interactive-hover' : 'text-txt-tertiary hover:text-txt-primary'
}`}
title="Input Devices"
>
@@ -763,8 +763,8 @@ function UserAreaPanel({
{/* Headphones */}
<button
onClick={onDeafenToggle}
className={`w-8 h-8 flex items-center justify-center hover:bg-discord-modifier-hover rounded-l-[4px] transition-colors ${
isDeafened ? 'text-discord-red' : 'text-discord-text-muted hover:text-discord-text-primary'
className={`w-8 h-8 flex items-center justify-center hover:bg-interactive-hover rounded-l-[4px] transition-colors ${
isDeafened ? 'text-txt-danger' : 'text-txt-tertiary hover:text-txt-primary'
}`}
title={isDeafened ? 'Undeafen' : 'Deafen'}
>
@@ -776,8 +776,8 @@ function UserAreaPanel({
{/* Output chevron */}
<button
onClick={() => togglePanel('output')}
className={`w-[18px] h-8 flex items-center justify-center hover:bg-discord-modifier-hover rounded-r-[4px] transition-colors ${
openPanel === 'output' ? 'text-discord-text-primary bg-discord-modifier-hover' : 'text-discord-text-muted hover:text-discord-text-primary'
className={`w-[18px] h-8 flex items-center justify-center hover:bg-interactive-hover rounded-r-[4px] transition-colors ${
openPanel === 'output' ? 'text-txt-primary bg-interactive-hover' : 'text-txt-tertiary hover:text-txt-primary'
}`}
title="Output Devices"
>
@@ -789,7 +789,7 @@ function UserAreaPanel({
{/* Settings */}
<button
onClick={onSettingsClick}
className="w-8 h-8 flex items-center justify-center text-discord-text-muted hover:text-discord-text-primary hover:bg-discord-modifier-hover rounded-[4px] transition-colors"
className="w-8 h-8 flex items-center justify-center text-txt-tertiary hover:text-txt-primary hover:bg-interactive-hover rounded-[4px] transition-colors"
title="User Settings"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
@@ -90,23 +90,23 @@ export function MainContent() {
return (
<div
ref={voiceContainerRef}
className={`flex-1 flex flex-col bg-[#0b0c0e] min-w-0 group/voice relative ${voiceFullscreen ? 'h-screen' : ''}`}
className={`flex-1 flex flex-col bg-surface-base min-w-0 group/voice relative ${voiceFullscreen ? 'h-screen' : ''}`}
>
<div className={`h-12 px-4 flex items-center justify-between shadow-header flex-shrink-0 bg-[#0b0c0e] transition-opacity duration-300 ${voiceFullscreen ? 'opacity-0 hover:opacity-100' : ''}`}>
<div className={`h-12 px-4 flex items-center justify-between shadow-header flex-shrink-0 bg-surface-base transition-opacity duration-300 ${voiceFullscreen ? 'opacity-0 hover:opacity-100' : ''}`}>
<div className="flex items-center gap-2">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" className="text-discord-text-muted">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" className="text-txt-tertiary">
<path d="M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z" />
</svg>
<span className="font-bold text-discord-text-primary">{dmName}</span>
<span className="font-bold text-txt-primary">{dmName}</span>
{connectionError ? (
<span className="text-xs text-discord-red font-medium ml-2">Connection Failed</span>
<span className="text-xs text-txt-danger font-medium ml-2">Connection Failed</span>
) : isLiveKitConnected ? (
<>
<span className="text-xs text-discord-green font-medium ml-2">Connected</span>
<span className="text-xs text-discord-text-muted ml-1">{participants.length} in call</span>
<span className="text-xs text-status-online font-medium ml-2">Connected</span>
<span className="text-xs text-txt-tertiary ml-1">{participants.length} in call</span>
</>
) : (
<span className="text-xs text-discord-yellow font-medium ml-2">Connecting...</span>
<span className="text-xs text-status-idle font-medium ml-2">Connecting...</span>
)}
</div>
<div className="flex items-center gap-1 flex-shrink-0">
@@ -127,38 +127,38 @@ export function MainContent() {
}
return (
<div className="flex-1 flex flex-col bg-discord-bg-primary min-w-0 relative">
<div className="flex-1 flex flex-col bg-surface-chat min-w-0 relative">
{isCallingThisDm && (
<div className="bg-discord-green/10 border-b border-discord-green/20 px-4 py-3 flex items-center justify-between">
<div className="bg-status-online/10 border-b border-status-online/20 px-4 py-3 flex items-center justify-between">
<div className="flex items-center gap-2">
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" className="text-discord-green animate-pulse">
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" className="text-status-online animate-pulse">
<path d="M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z" />
</svg>
<span className="text-discord-green text-sm font-medium">Calling {dmName}...</span>
<span className="text-status-online text-sm font-medium">Calling {dmName}...</span>
</div>
<button
onClick={handleCancelCall}
className="px-3 py-1 bg-discord-red hover:bg-discord-red/80 text-white text-xs font-medium rounded transition-colors"
className="px-3 py-1 bg-accent-rose hover:bg-accent-rose/80 text-white text-xs font-medium rounded transition-colors"
>
Cancel
</button>
</div>
)}
<div className="h-12 px-4 flex items-center justify-between shadow-header flex-shrink-0 z-10 bg-discord-bg-primary">
<div className="h-12 px-4 flex items-center justify-between shadow-header flex-shrink-0 z-10 bg-surface-chat">
<div className="flex items-center gap-2 min-w-0">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" className="text-discord-text-muted flex-shrink-0">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" className="text-txt-tertiary flex-shrink-0">
<path d="M12.5 2A6.5 6.5 0 0 0 6 8.5c0 1.82.75 3.47 1.95 4.65A10.02 10.02 0 0 0 2 22h2c0-4.42 3.58-8 8-8 .35 0 .69.03 1.03.07A6.49 6.49 0 0 0 19 8.5 6.5 6.5 0 0 0 12.5 2Zm0 11A4.5 4.5 0 1 1 17 8.5a4.5 4.5 0 0 1-4.5 4.5Z" />
</svg>
<span className="font-bold text-discord-text-primary truncate">{dmName}</span>
<span className="font-bold text-txt-primary truncate">{dmName}</span>
{isGroupDm && (
<span className="text-xs text-discord-text-muted flex-shrink-0">({dmChannel?.members.length} Members)</span>
<span className="text-xs text-txt-tertiary flex-shrink-0">({dmChannel?.members.length} Members)</span>
)}
</div>
<div className="flex items-center gap-1 flex-shrink-0">
<button
onClick={handleStartVoiceCall}
disabled={!!outgoingCall || !!activeDmCall}
className="w-8 h-8 flex items-center justify-center text-discord-text-muted hover:text-discord-text-primary transition-colors rounded-[4px] hover:bg-discord-modifier-hover disabled:opacity-50 disabled:cursor-not-allowed"
className="w-8 h-8 flex items-center justify-center text-txt-tertiary hover:text-txt-primary transition-colors rounded-[4px] hover:bg-interactive-hover disabled:opacity-50 disabled:cursor-not-allowed"
title="Start Voice Call"
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
@@ -168,14 +168,14 @@ export function MainContent() {
<button
onClick={handleStartVoiceCall}
disabled={!!outgoingCall || !!activeDmCall}
className="w-8 h-8 flex items-center justify-center text-discord-text-muted hover:text-discord-text-primary transition-colors rounded-[4px] hover:bg-discord-modifier-hover disabled:opacity-50 disabled:cursor-not-allowed"
className="w-8 h-8 flex items-center justify-center text-txt-tertiary hover:text-txt-primary transition-colors rounded-[4px] hover:bg-interactive-hover disabled:opacity-50 disabled:cursor-not-allowed"
title="Start Video Call"
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path d="M21 6.5l-4 4V7c0-.55-.45-1-1-1H9.82L21 17.18V6.5zM3.27 2L2 3.27 4.73 6H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.21 0 .39-.08.54-.18L19.73 21 21 19.73 3.27 2z" />
</svg>
</button>
<button className="w-8 h-8 flex items-center justify-center text-discord-text-muted hover:text-discord-text-primary transition-colors rounded-[4px] hover:bg-discord-modifier-hover" title="Pinned Messages">
<button className="w-8 h-8 flex items-center justify-center text-txt-tertiary hover:text-txt-primary transition-colors rounded-[4px] hover:bg-interactive-hover" title="Pinned Messages">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" transform="rotate(45 12 12)" />
<path d="M16 9V4h1c.55 0 1-.45 1-1s-.45-1-1-1H7c-.55 0-1 .45-1 1s.45 1 1 1h1v5c0 1.66-1.34 3-3 3v2h5.97v7l1 1 1-1v-7H19v-2c-1.66 0-3-1.34-3-3z" />
@@ -183,7 +183,7 @@ export function MainContent() {
</button>
<button
onClick={() => openModal('addDmMember', { dmChannelId: currentChannelId })}
className="w-8 h-8 flex items-center justify-center text-discord-text-muted hover:text-discord-text-primary transition-colors rounded-[4px] hover:bg-discord-modifier-hover"
className="w-8 h-8 flex items-center justify-center text-txt-tertiary hover:text-txt-primary transition-colors rounded-[4px] hover:bg-interactive-hover"
title="Add Friends to DM"
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
@@ -191,18 +191,18 @@ export function MainContent() {
</svg>
</button>
<MemberListToggleButton />
<div className="w-[1px] h-6 bg-discord-modifier-accent mx-1" />
<button className="w-8 h-8 flex items-center justify-center text-discord-text-muted hover:text-discord-text-primary transition-colors rounded-[4px] hover:bg-discord-modifier-hover" title="Search">
<div className="w-[1px] h-6 bg-interactive-muted mx-1" />
<button className="w-8 h-8 flex items-center justify-center text-txt-tertiary hover:text-txt-primary transition-colors rounded-[4px] hover:bg-interactive-hover" title="Search">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path d="M21.707 20.293l-5.395-5.395A7.457 7.457 0 0018 10.5 7.5 7.5 0 1010.5 18c1.575 0 3.027-.486 4.228-1.31l5.476 5.476a.997.997 0 001.414 0l.089-.089a1 1 0 000-1.414l.001-.37zM10.5 16a5.5 5.5 0 110-11 5.5 5.5 0 010 11z" />
</svg>
</button>
<button className="w-8 h-8 flex items-center justify-center text-discord-text-muted hover:text-discord-text-primary transition-colors rounded-[4px] hover:bg-discord-modifier-hover" title="Inbox">
<button className="w-8 h-8 flex items-center justify-center text-txt-tertiary hover:text-txt-primary transition-colors rounded-[4px] hover:bg-interactive-hover" title="Inbox">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 3H4.99c-1.11 0-1.98.9-1.98 2L3 19c0 1.1.88 2 1.99 2H19c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 12h-4c0 1.66-1.35 3-3 3s-3-1.34-3-3H4.99V5H19v10z" />
</svg>
</button>
<button className="w-8 h-8 flex items-center justify-center text-discord-text-muted hover:text-discord-text-primary transition-colors rounded-[4px] hover:bg-discord-modifier-hover" title="Help">
<button className="w-8 h-8 flex items-center justify-center text-txt-tertiary hover:text-txt-primary transition-colors rounded-[4px] hover:bg-interactive-hover" title="Help">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z" />
</svg>
@@ -218,14 +218,14 @@ export function MainContent() {
if (!currentChannelId || !channel) {
return (
<div className="flex-1 flex flex-col bg-discord-bg-primary">
<div className="flex-1 flex flex-col bg-surface-chat relative">
<div className="h-12 px-4 flex items-center justify-between shadow-header">
<span className="text-discord-text-muted">Select a channel</span>
<span className="text-txt-tertiary">Select a channel</span>
<div className="flex items-center gap-1 flex-shrink-0">
<MemberListToggleButton />
</div>
</div>
<div className="flex-1 flex items-center justify-center text-discord-text-muted">
<div className="flex-1 flex items-center justify-center text-txt-tertiary">
<p>Select a text or voice channel to get started</p>
</div>
</div>
@@ -237,30 +237,30 @@ export function MainContent() {
if (!isInThisChannel) {
return (
<div className="flex-1 flex flex-col bg-[#0b0c0e]">
<div className="h-12 px-4 flex items-center justify-between shadow-header flex-shrink-0 bg-[#0b0c0e]">
<div className="flex-1 flex flex-col bg-surface-base">
<div className="h-12 px-4 flex items-center justify-between shadow-header flex-shrink-0 bg-surface-base">
<div className="flex items-center gap-2">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" className="text-discord-text-muted">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" className="text-txt-tertiary">
<path d="M11 5L6 9H2V15H6L11 19V5ZM15.54 8.46C16.48 9.4 17 10.67 17 12S16.48 14.6 15.54 15.54L14.12 14.12C14.69 13.55 15 12.79 15 12S14.69 10.45 14.12 9.88L15.54 8.46Z" />
</svg>
<span className="font-bold text-discord-text-primary">{channel.name}</span>
<span className="font-bold text-txt-primary">{channel.name}</span>
</div>
<div className="flex items-center gap-1 flex-shrink-0">
<MemberListToggleButton />
</div>
</div>
<div className="flex-1 flex flex-col items-center justify-center gap-8 relative">
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,rgba(88,101,242,0.12)_0%,transparent_70%)] animate-gradient-pulse pointer-events-none" />
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,rgba(124,108,246,0.12)_0%,transparent_70%)] animate-gradient-pulse pointer-events-none" />
<div className="text-center relative z-10">
<h2 className="text-[28px] font-bold text-white mb-3">{channel.name}</h2>
<p className="text-discord-text-muted text-[15px]">No one is currently in this voice channel.</p>
<p className="text-txt-tertiary text-[15px]">No one is currently in this voice channel.</p>
</div>
<button
onClick={() => {
useVoiceStore.getState().setCurrentVoiceChannel(currentChannelId);
wsSend({ type: 'voice_join', channelId: currentChannelId });
}}
className="relative z-10 px-8 py-3 bg-gradient-to-r from-[#5865f2] to-[#7b6cf6] hover:brightness-110 text-white font-semibold rounded-full transition-all text-[15px] shadow-[0_4px_20px_rgba(88,101,242,0.3)]"
className="relative z-10 px-8 py-3 bg-accent-primary hover:bg-accent-primary-hover text-white font-semibold rounded-full transition-all text-[15px] shadow-[0_4px_20px_rgba(124,108,246,0.3)]"
>
Join Voice
</button>
@@ -272,23 +272,23 @@ export function MainContent() {
return (
<div
ref={voiceContainerRef}
className={`flex-1 flex flex-col bg-[#0b0c0e] min-w-0 group/voice relative ${voiceFullscreen ? 'h-screen' : ''}`}
className={`flex-1 flex flex-col bg-surface-base min-w-0 group/voice relative ${voiceFullscreen ? 'h-screen' : ''}`}
>
<div className={`h-12 px-4 flex items-center justify-between shadow-header flex-shrink-0 bg-[#0b0c0e] transition-opacity duration-300 ${voiceFullscreen ? 'opacity-0 hover:opacity-100' : ''}`}>
<div className={`h-12 px-4 flex items-center justify-between shadow-header flex-shrink-0 bg-surface-base transition-opacity duration-300 ${voiceFullscreen ? 'opacity-0 hover:opacity-100' : ''}`}>
<div className="flex items-center gap-2">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" className="text-discord-text-muted">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" className="text-txt-tertiary">
<path d="M11 5L6 9H2V15H6L11 19V5ZM15.54 8.46C16.48 9.4 17 10.67 17 12S16.48 14.6 15.54 15.54L14.12 14.12C14.69 13.55 15 12.79 15 12S14.69 10.45 14.12 9.88L15.54 8.46Z" />
</svg>
<span className="font-bold text-discord-text-primary">{channel.name}</span>
<span className="font-bold text-txt-primary">{channel.name}</span>
{connectionError ? (
<span className="text-xs text-discord-red font-medium ml-2">Connection Failed</span>
<span className="text-xs text-txt-danger font-medium ml-2">Connection Failed</span>
) : isLiveKitConnected ? (
<>
<span className="text-xs text-discord-green font-medium ml-2">Connected</span>
<span className="text-xs text-discord-text-muted ml-1">{participants.length} connected</span>
<span className="text-xs text-status-online font-medium ml-2">Connected</span>
<span className="text-xs text-txt-tertiary ml-1">{participants.length} connected</span>
</>
) : (
<span className="text-xs text-discord-yellow font-medium ml-2">Connecting...</span>
<span className="text-xs text-status-idle font-medium ml-2">Connecting...</span>
)}
</div>
<div className="flex items-center gap-1 flex-shrink-0">
@@ -309,49 +309,49 @@ export function MainContent() {
}
return (
<div className="flex-1 flex flex-col bg-discord-bg-primary min-w-0 relative">
<div className="h-12 px-4 flex items-center justify-between shadow-header flex-shrink-0 z-10 bg-discord-bg-primary">
<div className="flex-1 flex flex-col bg-surface-chat min-w-0 relative">
<div className="h-12 px-4 flex items-center justify-between shadow-header flex-shrink-0 z-10 bg-surface-chat">
<div className="flex items-center gap-2 min-w-0">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" className="text-discord-text-muted flex-shrink-0">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" className="text-txt-tertiary flex-shrink-0">
<path d="M5.88657 21C5.57547 21 5.3399 20.7189 5.39427 20.4126L6.00001 17H2.59511C2.28449 17 2.04905 16.7198 2.10259 16.4138L2.27759 15.4138C2.31946 15.1746 2.52722 15 2.77011 15H6.35001L7.41001 9H4.00511C3.69449 9 3.45905 8.71977 3.51259 8.41381L3.68759 7.41381C3.72946 7.17456 3.93722 7 4.18011 7H7.76001L8.39677 3.41262C8.43914 3.17391 8.64664 3 8.88907 3H9.87344C10.1845 3 10.4201 3.28107 10.3657 3.58738L9.76001 7H15.76L16.3968 3.41262C16.4391 3.17391 16.6466 3 16.8891 3H17.8734C18.1845 3 18.4201 3.28107 18.3657 3.58738L17.76 7H21.1649C21.4755 7 21.711 7.28023 21.6574 7.58619L21.4824 8.58619C21.4406 8.82544 21.2328 9 20.9899 9H17.41L16.35 15H19.7549C20.0655 15 20.301 15.2802 20.2474 15.5862L20.0724 16.5862C20.0306 16.8254 19.8228 17 19.5799 17H16L15.3632 20.5874C15.3209 20.8261 15.1134 21 14.8709 21H13.8866C13.5755 21 13.3399 20.7189 13.3943 20.4126L14 17H8.00001L7.36325 20.5874C7.32088 20.8261 7.11337 21 6.87094 21H5.88657ZM9.41001 9L8.35001 15H14.35L15.41 9H9.41001Z" />
</svg>
<span className="font-bold text-discord-text-primary truncate leading-tight">{channel.name}</span>
<span className="font-bold text-txt-primary truncate leading-tight">{channel.name}</span>
{channel.topic && (
<>
<div className="w-[1px] h-6 bg-discord-bg-accent mx-2" />
<span className="text-xs text-discord-text-muted truncate leading-tight">{channel.topic}</span>
<div className="w-[1px] h-6 bg-interactive-muted mx-2" />
<span className="text-xs text-txt-tertiary truncate leading-tight">{channel.topic}</span>
</>
)}
</div>
<div className="flex items-center gap-1 flex-shrink-0">
<button className="w-8 h-8 flex items-center justify-center text-discord-text-muted hover:text-discord-text-primary transition-colors rounded-[4px] hover:bg-discord-modifier-hover" title="Threads">
<button className="w-8 h-8 flex items-center justify-center text-txt-tertiary hover:text-txt-primary transition-colors rounded-[4px] hover:bg-interactive-hover" title="Threads">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path d="M5.43 21a.996.996 0 01-.98-.8l-.79-4.34H2.5a1 1 0 110-2h.93l-.55-3H1.5a1 1 0 010-2h1.15L1.87 4.86a1 1 0 011.96-.72L4.6 8.86h3.32l-.78-4.72a1 1 0 011.96-.28l.84 5H13.5a1 1 0 110 2h-3.33l.55 3H13.5a1 1 0 110 2h-2.55l.72 3.94a1 1 0 01-.79 1.16 1.034 1.034 0 01-.18.02.996.996 0 01-.98-.82L8.95 15.86H5.63l.72 3.94A1 1 0 015.43 21zM5.86 10.86l.55 3h3.32l-.55-3H5.86z" />
</svg>
</button>
<button className="w-8 h-8 flex items-center justify-center text-discord-text-muted hover:text-discord-text-primary transition-colors rounded-[4px] hover:bg-discord-modifier-hover" title="Notification Settings">
<button className="w-8 h-8 flex items-center justify-center text-txt-tertiary hover:text-txt-primary transition-colors rounded-[4px] hover:bg-interactive-hover" title="Notification Settings">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z" />
</svg>
</button>
<button className="w-8 h-8 flex items-center justify-center text-discord-text-muted hover:text-discord-text-primary transition-colors rounded-[4px] hover:bg-discord-modifier-hover" title="Pinned Messages">
<button className="w-8 h-8 flex items-center justify-center text-txt-tertiary hover:text-txt-primary transition-colors rounded-[4px] hover:bg-interactive-hover" title="Pinned Messages">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path d="M16 9V4h1c.55 0 1-.45 1-1s-.45-1-1-1H7c-.55 0-1 .45-1 1s.45 1 1 1h1v5c0 1.66-1.34 3-3 3v2h5.97v7l1 1 1-1v-7H19v-2c-1.66 0-3-1.34-3-3z" />
</svg>
</button>
<MemberListToggleButton />
<div className="w-[1px] h-6 bg-discord-modifier-accent mx-1" />
<button className="w-8 h-8 flex items-center justify-center text-discord-text-muted hover:text-discord-text-primary transition-colors rounded-[4px] hover:bg-discord-modifier-hover" title="Search">
<div className="w-[1px] h-6 bg-interactive-muted mx-1" />
<button className="w-8 h-8 flex items-center justify-center text-txt-tertiary hover:text-txt-primary transition-colors rounded-[4px] hover:bg-interactive-hover" title="Search">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path d="M21.707 20.293l-5.395-5.395A7.457 7.457 0 0018 10.5 7.5 7.5 0 1010.5 18c1.575 0 3.027-.486 4.228-1.31l5.476 5.476a.997.997 0 001.414 0l.089-.089a1 1 0 000-1.414l.001-.37zM10.5 16a5.5 5.5 0 110-11 5.5 5.5 0 010 11z" />
</svg>
</button>
<button className="w-8 h-8 flex items-center justify-center text-discord-text-muted hover:text-discord-text-primary transition-colors rounded-[4px] hover:bg-discord-modifier-hover" title="Inbox">
<button className="w-8 h-8 flex items-center justify-center text-txt-tertiary hover:text-txt-primary transition-colors rounded-[4px] hover:bg-interactive-hover" title="Inbox">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 3H4.99c-1.11 0-1.98.9-1.98 2L3 19c0 1.1.88 2 1.99 2H19c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 12h-4c0 1.66-1.35 3-3 3s-3-1.34-3-3H4.99V5H19v10z" />
</svg>
</button>
<button className="w-8 h-8 flex items-center justify-center text-discord-text-muted hover:text-discord-text-primary transition-colors rounded-[4px] hover:bg-discord-modifier-hover" title="Help">
<button className="w-8 h-8 flex items-center justify-center text-txt-tertiary hover:text-txt-primary transition-colors rounded-[4px] hover:bg-interactive-hover" title="Help">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z" />
</svg>
@@ -8,8 +8,8 @@ export function MemberListToggleButton() {
return (
<button
onClick={toggleMemberList}
className={`w-8 h-8 flex items-center justify-center transition-colors rounded-[4px] hover:bg-discord-modifier-hover ${
memberListOpen ? 'text-discord-text-primary' : 'text-discord-text-muted hover:text-discord-text-secondary'
className={`w-8 h-8 flex items-center justify-center transition-colors rounded-[4px] hover:bg-interactive-hover ${
memberListOpen ? 'text-txt-primary' : 'text-txt-tertiary hover:text-txt-secondary'
}`}
title="Toggle Member List"
>
@@ -15,7 +15,7 @@ function getMemberGroup(member: MemberWithUser, ownerId: string | undefined) {
return {
key: '__owner__',
label: 'OWNER',
color: ownerRole?.color ?? '#f23f43',
color: ownerRole?.color ?? 'var(--accent-rose)',
position: Infinity,
};
}
@@ -79,7 +79,7 @@ export function MemberSidebar() {
return { color: sorted[0]!.color };
}
if (ownerId && member.userId === ownerId) {
return { color: '#f23f43' };
return { color: 'var(--accent-rose)' };
}
return undefined;
};
@@ -100,7 +100,7 @@ export function MemberSidebar() {
<div
key={member.userId}
onClick={(e) => handleMemberClick(e, member.user)}
className="flex items-center gap-3 px-2 py-1.5 rounded-[4px] hover:bg-discord-modifier-hover cursor-pointer group transition-colors"
className="flex items-center gap-3 px-2 py-1.5 rounded-[4px] hover:bg-interactive-hover cursor-pointer group transition-colors"
>
<Avatar
src={member.user.avatar}
@@ -111,13 +111,13 @@ export function MemberSidebar() {
/>
<div className="flex-1 min-w-0">
<div
className={`text-[15px] font-medium truncate ${isOffline ? 'text-discord-text-muted' : (!colorStyle ? 'text-discord-text-primary' : '')}`}
className={`text-[15px] font-medium truncate ${isOffline ? 'text-txt-tertiary' : (!colorStyle ? 'text-txt-primary' : '')}`}
style={colorStyle}
>
{displayName}
</div>
{!isOffline && member.user.customStatus && (
<div className="text-[12px] text-discord-text-muted truncate">{member.user.customStatus}</div>
<div className="text-[12px] text-txt-tertiary truncate">{member.user.customStatus}</div>
)}
</div>
</div>
@@ -125,12 +125,12 @@ export function MemberSidebar() {
};
return (
<div className="w-60 bg-discord-bg-members flex-shrink-0 overflow-y-auto select-none no-scrollbar">
<div className="w-60 bg-surface-members flex-shrink-0 overflow-y-auto select-none no-scrollbar hidden md:block">
<div className="p-3">
{/* Role-based groups */}
{roleGroups.map(([key, group]) => (
<div key={key} className="mb-4">
<h3 className="text-[12px] font-bold text-discord-text-muted uppercase tracking-wider px-2 mb-1">
<h3 className="text-[12px] font-bold text-txt-tertiary uppercase tracking-wider px-2 mb-1">
{group.label} {group.members.length}
</h3>
{group.members.map((m) => renderMember(m))}
@@ -140,7 +140,7 @@ export function MemberSidebar() {
{/* Offline */}
{offlineMembers.length > 0 && (
<div>
<h3 className="text-[12px] font-bold text-discord-text-muted uppercase tracking-wider px-2 mb-1">
<h3 className="text-[12px] font-bold text-txt-tertiary uppercase tracking-wider px-2 mb-1">
OFFLINE {offlineMembers.length}
</h3>
{offlineMembers.map((m) => renderMember(m, true))}
@@ -13,7 +13,7 @@ export function MobileNav() {
{/* Hamburger button in header */}
<button
onClick={toggleSidebar}
className="fixed top-3 left-3 z-40 p-1.5 rounded bg-discord-bg-secondary text-discord-text-primary md:hidden"
className="fixed top-3 left-3 z-[120] p-1.5 rounded bg-surface-channel text-txt-primary md:hidden"
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
{sidebarOpen ? (
@@ -27,7 +27,7 @@ export function MobileNav() {
{/* Backdrop when sidebar is open on mobile */}
{sidebarOpen && (
<div
className="fixed inset-0 bg-black/50 z-30 md:hidden"
className="fixed inset-0 bg-black/50 z-[35] md:hidden"
onClick={toggleSidebar}
/>
)}
@@ -31,14 +31,14 @@ function SidebarItem({ name, icon, active, onClick, type = 'server', actionType,
const base = 'w-12 h-12 flex items-center justify-center transition-all duration-200 overflow-hidden relative group';
if (type === 'dm') {
return `${base} ${active ? 'bg-discord-blurple rounded-[16px] text-white' : 'bg-discord-bg-primary rounded-[24px] hover:rounded-[16px] text-discord-text-primary hover:bg-discord-blurple hover:text-white'}`;
}
if (type === 'action') {
return `${base} bg-discord-bg-primary rounded-[24px] hover:rounded-[16px] text-discord-green hover:bg-discord-green hover:text-white`;
return `${base} ${active ? 'bg-accent-primary rounded-[16px] text-white' : 'bg-surface-chat rounded-[24px] hover:rounded-[16px] text-txt-primary hover:bg-accent-primary-hover hover:text-white'}`;
}
return `${base} ${active ? 'bg-discord-blurple rounded-[16px] text-white' : 'bg-discord-bg-primary rounded-[24px] hover:rounded-[16px] text-discord-text-primary hover:bg-discord-blurple hover:text-white'}`;
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} ${active ? 'bg-accent-primary rounded-[16px] text-white' : 'bg-surface-chat rounded-[24px] hover:rounded-[16px] text-txt-primary hover:bg-accent-primary-hover hover:text-white'}`;
};
return (
@@ -134,7 +134,7 @@ export function ServerSidebar() {
};
return (
<nav className="w-[72px] bg-discord-bg-server flex flex-col items-center py-3 overflow-y-auto flex-shrink-0 no-scrollbar select-none">
<nav className="w-[72px] bg-surface-base flex flex-col items-center py-3 overflow-y-auto flex-shrink-0 no-scrollbar select-none md:fixed md:inset-y-0 md:left-0 md:z-[100] md:glass-strip">
<SidebarItem
id="@me"
name="Direct Messages"
@@ -144,7 +144,7 @@ export function ServerSidebar() {
hasUnread={hasDmUnread}
/>
<div className="w-8 h-[2px] bg-discord-modifier-accent rounded-full mb-2" />
<div className="w-8 h-[2px] bg-interactive-muted rounded-full mb-2" />
{servers.map((server) => (
<SidebarItem
+5 -5
View File
@@ -25,18 +25,18 @@ export function Modal({ isOpen, onClose, title, children, maxWidth = 'max-w-md'
if (!isOpen) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center animate-fade-in">
<div className="fixed inset-0 z-[200] flex items-center justify-center animate-fade-in">
<div
className="absolute inset-0 bg-discord-bg-overlay"
className="absolute inset-0 bg-surface-overlay"
onClick={onClose}
/>
<div className={`relative ${maxWidth} w-full mx-4 bg-discord-bg-surface rounded-lg shadow-xl animate-slide-up`}>
<div className={`relative ${maxWidth} w-full mx-4 bg-surface-elevated rounded-lg shadow-xl animate-slide-up`}>
{title && (
<div className="flex items-center justify-between px-4 pt-4">
<h2 className="text-xl font-bold text-discord-text-primary">{title}</h2>
<h2 className="text-xl font-bold text-txt-primary">{title}</h2>
<button
onClick={onClose}
className="text-discord-text-muted hover:text-discord-text-primary transition-colors p-1"
className="text-txt-tertiary hover:text-txt-primary transition-colors p-1"
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path d="M18.4 4L12 10.4L5.6 4L4 5.6L10.4 12L4 18.4L5.6 20L12 13.6L18.4 20L20 18.4L13.6 12L20 5.6L18.4 4Z" />
@@ -79,28 +79,28 @@ export function VoiceControls() {
};
const statusColor = connectionError
? 'text-discord-red'
? 'text-txt-danger'
: isLiveKitConnected
? 'text-discord-green'
: 'text-discord-yellow';
? 'text-status-online'
: 'text-status-idle';
const statusBgColor = connectionError
? 'bg-discord-red/20'
? 'bg-accent-rose/20'
: isLiveKitConnected
? 'bg-discord-green/20'
: 'bg-discord-yellow/20';
? 'bg-status-online/20'
: 'bg-status-idle/20';
const qualityColor =
connectionQuality === 'excellent' || connectionQuality === 'good'
? 'text-discord-green'
? 'text-status-online'
: connectionQuality === 'poor'
? 'text-discord-yellow'
? 'text-status-idle'
: connectionQuality === 'lost'
? 'text-discord-red'
? 'text-txt-danger'
: statusColor; // 'unknown' falls back to connection-state color
const btnBase = 'flex-1 h-[34px] flex items-center justify-center rounded-[4px] transition-colors';
const btnDefaultStyle = 'bg-[#111214] text-discord-text-muted hover:bg-[#1a1b1e] hover:text-discord-text-secondary';
const btnDefaultStyle = 'bg-surface-base text-txt-tertiary hover:bg-surface-channel hover:text-txt-secondary';
return (
<>
@@ -123,7 +123,7 @@ export function VoiceControls() {
<div className={`text-[13px] font-semibold leading-[18px] ${statusColor}`}>
{connectionError ? 'Connection Failed' : isLiveKitConnected ? 'Voice Connected' : 'Connecting...'}
</div>
<div className="text-[12px] text-discord-channels-default truncate leading-[16px]">
<div className="text-[12px] text-txt-tertiary truncate leading-[16px]">
{connectionError ? connectionError : channelName}
</div>
</div>
@@ -131,7 +131,7 @@ export function VoiceControls() {
<div className="flex items-center gap-0.5 flex-shrink-0">
<button
onClick={handleDisconnect}
className="w-7 h-7 flex items-center justify-center text-discord-text-muted hover:text-discord-text-primary transition-colors rounded"
className="w-7 h-7 flex items-center justify-center text-txt-tertiary hover:text-txt-primary transition-colors rounded"
title="Disconnect"
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
@@ -153,7 +153,7 @@ export function VoiceControls() {
onClick={handleCamera}
className={`${btnBase} ${
isCameraOn
? 'bg-[#111214] text-discord-green hover:bg-[#1a1b1e]'
? 'bg-surface-base text-status-online hover:bg-surface-channel'
: btnDefaultStyle
}`}
title={isCameraOn ? 'Turn Off Camera' : 'Turn On Camera'}
@@ -174,7 +174,7 @@ export function VoiceControls() {
onClick={handleScreenShare}
className={`${btnBase} ${
isScreenSharing
? 'bg-[#111214] text-discord-green hover:bg-[#1a1b1e]'
? 'bg-surface-base text-status-online hover:bg-surface-channel'
: btnDefaultStyle
}`}
title={isScreenSharing ? 'Stop Sharing' : 'Share Screen'}
@@ -193,7 +193,7 @@ export function VoiceControls() {
}}
className={`${btnBase} ${
showScreenShareSettings
? 'bg-[#111214] text-discord-blurple hover:bg-[#1a1b1e]'
? 'bg-surface-base text-accent-primary hover:bg-surface-channel'
: btnDefaultStyle
}`}
title="Video Quality"
@@ -209,7 +209,7 @@ export function VoiceControls() {
onClick={() => setRnnoiseEnabled(!rnnoiseEnabled)}
className={`${btnBase} ${
rnnoiseEnabled
? 'bg-[#111214] text-discord-green hover:bg-[#1a1b1e]'
? 'bg-surface-base text-status-online hover:bg-surface-channel'
: btnDefaultStyle
}`}
title={rnnoiseEnabled ? 'Disable AI Noise Suppression' : 'Enable AI Noise Suppression'}