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
+5 -1
View File
@@ -1,9 +1,13 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" data-theme="aether-drift">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="color-scheme" content="dark" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
<title>Backspace</title> <title>Backspace</title>
</head> </head>
<body class="bg-discord-bg-primary text-discord-text-primary"> <body class="bg-discord-bg-primary text-discord-text-primary">
@@ -216,16 +216,16 @@ export function MessageInput({ channelId, channelName }: MessageInputProps) {
}; };
return ( 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 && ( {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="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-discord-text-normal truncate"> <div className="flex items-center gap-1 text-[14px] text-txt-message truncate">
<span className="opacity-60">Replying to</span> <span className="opacity-60">Replying to</span>
<span className="font-bold">{replyTo.user.displayName ?? replyTo.user.username}</span> <span className="font-bold">{replyTo.user.displayName ?? replyTo.user.username}</span>
</div> </div>
<button <button
onClick={() => setReplyTo(null)} 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"> <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" /> <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>
)} )}
<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} onDrop={handleDrop}
onDragOver={handleDragOver} onDragOver={handleDragOver}
> >
@@ -249,9 +249,9 @@ export function MessageInput({ channelId, channelName }: MessageInputProps) {
{/* File previews */} {/* File previews */}
{files.length > 0 && ( {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) => ( {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/') ? ( {file.type.startsWith('image/') ? (
<img <img
src={URL.createObjectURL(file)} src={URL.createObjectURL(file)}
@@ -259,7 +259,7 @@ export function MessageInput({ channelId, channelName }: MessageInputProps) {
className="max-h-[150px] rounded object-cover" 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"> <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" /> <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> </svg>
@@ -268,7 +268,7 @@ export function MessageInput({ channelId, channelName }: MessageInputProps) {
)} )}
<button <button
onClick={() => removeFile(i)} 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"> <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" /> <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 */} {/* File attach button */}
<button <button
onClick={() => fileInputRef.current?.click()} 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" title="Attach file"
> >
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"> <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
@@ -312,14 +312,14 @@ export function MessageInput({ channelId, channelName }: MessageInputProps) {
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
onPaste={handlePaste} onPaste={handlePaste}
placeholder={`Message ${channelName.startsWith('@') ? channelName : `#${channelName}`}`} 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} rows={1}
disabled={isUploading} disabled={isUploading}
/> />
{/* Send indicator */} {/* Send indicator */}
{isUploading && ( {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"> <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" /> <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" /> <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 */} {/* 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"> <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" /> <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> </svg>
</button> </button>
{/* Sticker 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"> <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" /> <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> </svg>
</button> </button>
{/* Emoji 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"> <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" /> <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> </svg>
@@ -124,7 +124,7 @@ export function MessageList({ channelId }: MessageListProps) {
{!hasMore && <WelcomeHeader channelId={channelId} />} {!hasMore && <WelcomeHeader channelId={channelId} />}
<div className="pb-6"> <div className="pb-6 md:pb-[88px]">
{messages.map((msg, i) => { {messages.map((msg, i) => {
const prevMsg = messages[i - 1]; const prevMsg = messages[i - 1];
const showDate = shouldShowDateDivider(prevMsg, msg); const showDate = shouldShowDateDivider(prevMsg, msg);
@@ -134,11 +134,11 @@ export function MessageList({ channelId }: MessageListProps) {
<React.Fragment key={msg.id}> <React.Fragment key={msg.id}>
{showDate && ( {showDate && (
<div className="flex items-center px-4 my-6 select-none pointer-events-none"> <div className="flex items-center px-4 my-6 select-none pointer-events-none">
<div className="flex-1 h-[1px] bg-discord-modifier-accent" /> <div className="flex-1 h-[1px] bg-interactive-muted" />
<span className="px-2 text-[12px] font-bold text-discord-text-muted leading-tight"> <span className="px-2 text-[12px] font-bold text-txt-tertiary leading-tight">
{formatDateDivider(msg.createdAt)} {formatDateDivider(msg.createdAt)}
</span> </span>
<div className="flex-1 h-[1px] bg-discord-modifier-accent" /> <div className="flex-1 h-[1px] bg-interactive-muted" />
</div> </div>
)} )}
<Message <Message
@@ -175,35 +175,35 @@ function WelcomeHeader({ channelId }: { channelId: string }) {
<div className="mb-2"> <div className="mb-2">
<Avatar src={otherUser?.avatar} name={displayName} size={80} /> <Avatar src={otherUser?.avatar} name={displayName} size={80} />
</div> </div>
<h3 className="text-[32px] leading-10 font-bold text-discord-text-primary">{displayName}</h3> <h3 className="text-[32px] leading-10 font-bold text-txt-primary">{displayName}</h3>
<p className="text-discord-text-secondary text-[14px] mt-1"> <p className="text-txt-secondary text-[14px] mt-1">
This is the beginning of your direct message history with <strong>@{username}</strong>. This is the beginning of your direct message history with <strong>@{username}</strong>.
</p> </p>
{isFriend && otherUser && ( {isFriend && otherUser && (
<div className="mt-4"> <div className="mt-4">
<button <button
onClick={() => removeFriend(otherUser.id)} 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 Remove Friend
</button> </button>
</div> </div>
)} )}
<div className="mt-6 border-b border-discord-modifier-accent" /> <div className="mt-6 border-b border-interactive-muted" />
</div> </div>
); );
} }
return ( return (
<div className="px-4 pt-8 pb-4"> <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"> <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" /> <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> </svg>
</div> </div>
<h3 className="text-[32px] leading-10 font-bold text-discord-text-primary">Welcome to the channel!</h3> <h3 className="text-[32px] leading-10 font-bold text-txt-primary">Welcome to the channel!</h3>
<p className="text-discord-text-secondary text-[16px] mt-2">This is the start of the conversation.</p> <p className="text-txt-secondary text-[16px] mt-2">This is the start of the conversation.</p>
<div className="mt-6 border-b border-discord-modifier-accent" /> <div className="mt-6 border-b border-interactive-muted" />
</div> </div>
); );
} }
@@ -46,7 +46,7 @@ export function ActivityPanel() {
<div <div
key={friend.id} key={friend.id}
onClick={(e) => handleFriendClick(e, friend)} 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 <Avatar
src={friend.avatar} src={friend.avatar}
@@ -56,25 +56,25 @@ export function ActivityPanel() {
className={isOffline ? 'opacity-60' : undefined} className={isOffline ? 'opacity-60' : undefined}
/> />
<div className="flex-1 min-w-0"> <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} {friend.displayName ?? friend.username}
</div> </div>
{!isOffline && friend.customStatus && ( {!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>
</div> </div>
); );
return ( 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"> <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 ? ( {onlineFriends.length === 0 && offlineFriends.length === 0 ? (
<div className="text-center py-8"> <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-[16px] font-bold text-txt-primary mb-1">It's quiet for now...</div>
<div className="text-[14px] text-discord-text-muted max-w-[200px] mx-auto"> <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! When a friend starts an activity&#8212;like playing a game or hanging out on voice&#8212;we'll show it here!
</div> </div>
</div> </div>
@@ -82,7 +82,7 @@ export function ActivityPanel() {
<> <>
{onlineFriends.length > 0 && ( {onlineFriends.length > 0 && (
<div className="mb-4"> <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} ONLINE {onlineFriends.length}
</h3> </h3>
{onlineFriends.map(f => renderFriend(f))} {onlineFriends.map(f => renderFriend(f))}
@@ -90,7 +90,7 @@ export function ActivityPanel() {
)} )}
{offlineFriends.length > 0 && ( {offlineFriends.length > 0 && (
<div> <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} OFFLINE {offlineFriends.length}
</h3> </h3>
{offlineFriends.map(f => renderFriend(f, true))} {offlineFriends.map(f => renderFriend(f, true))}
@@ -204,28 +204,28 @@ export function AppLayout() {
if (isLoading || !user) { if (isLoading || !user) {
return ( 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"> <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" /> <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" /> <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
</svg> </svg>
<p className="text-discord-text-muted">Loading Backspace...</p> <p className="text-txt-tertiary">Loading Backspace...</p>
</div> </div>
</div> </div>
); );
} }
return ( 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 */} {/* 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 /> <ServerSidebar />
<ChannelSidebar /> <ChannelSidebar />
</div> </div>
{/* Main content area */} {/* 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 /> <MainContent />
<RightPanel /> <RightPanel />
</div> </div>
@@ -250,7 +250,7 @@ export function AppLayout() {
{userProfilePopout.user && userProfilePopout.position && ( {userProfilePopout.user && userProfilePopout.position && (
<> <>
<div <div
className="fixed inset-0 z-[45]" className="fixed inset-0 z-[145]"
onClick={closeUserProfile} onClick={closeUserProfile}
/> />
<UserProfilePopout <UserProfilePopout
@@ -97,8 +97,8 @@ export function ChannelSidebar() {
// Floating bottom panel — shared between DM view and server view // Floating bottom panel — shared between DM view and server view
const floatingPanel = user ? ( const floatingPanel = user ? (
<div className="fixed bottom-2 left-2 z-30 w-[296px]"> <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="bg-[#1a1b1e] rounded-lg shadow-[0_4px_20px_rgba(0,0,0,0.5)] ring-1 ring-white/[0.06]"> <div className="glass-bubble rounded-[14px]">
{/* Voice controls (expands when connected) */} {/* Voice controls (expands when connected) */}
{(currentVoiceChannelId || activeDmCall) && <VoiceControls />} {(currentVoiceChannelId || activeDmCall) && <VoiceControls />}
{/* Separator between voice and user area */} {/* Separator between voice and user area */}
@@ -119,9 +119,9 @@ export function ChannelSidebar() {
if (!server) { if (!server) {
return ( 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"> <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 Find or start a conversation
</button> </button>
</div> </div>
@@ -130,8 +130,8 @@ export function ChannelSidebar() {
onClick={handleHomeClick} onClick={handleHomeClick}
className={`flex items-center gap-3 px-2 h-[42px] rounded-[4px] cursor-pointer mb-[2px] transition-colors group ${ className={`flex items-center gap-3 px-2 h-[42px] rounded-[4px] cursor-pointer mb-[2px] transition-colors group ${
!currentChannelId !currentChannelId
? 'bg-discord-modifier-selected text-white' ? 'bg-interactive-selected text-white'
: 'text-discord-text-muted hover:bg-discord-modifier-hover hover:text-discord-text-secondary' : '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'}`}> <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 */} {/* Nitro */}
<div <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"> <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)" /> <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 */} {/* Shop */}
<div <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"> <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" /> <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>
<div className="mt-[18px] px-2 mb-1 flex items-center justify-between group"> <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 <button
onClick={() => openModal('newDm')} 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" title="New Direct Message"
> >
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
@@ -193,10 +193,10 @@ export function ChannelSidebar() {
onClick={() => handleChannelClick(dm.id)} onClick={() => handleChannelClick(dm.id)}
className={`relative flex items-center gap-3 px-2 h-[42px] rounded-[4px] cursor-pointer transition-colors group ${ className={`relative flex items-center gap-3 px-2 h-[42px] rounded-[4px] cursor-pointer transition-colors group ${
currentChannelId === dm.id currentChannelId === dm.id
? 'bg-discord-modifier-selected text-white' ? 'bg-interactive-selected text-white'
: isDmUnread : isDmUnread
? 'text-white hover:bg-discord-modifier-hover' ? 'text-white hover:bg-interactive-hover'
: 'text-discord-text-muted hover:bg-discord-modifier-hover hover:text-discord-text-secondary' : 'text-txt-tertiary hover:bg-interactive-hover hover:text-txt-secondary'
}`} }`}
> >
{isDmUnread && ( {isDmUnread && (
@@ -207,7 +207,7 @@ export function ChannelSidebar() {
{otherMembers.slice(0, 2).map((m, i) => ( {otherMembers.slice(0, 2).map((m, i) => (
<div <div
key={m.id} 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={{ style={{
width: 22, height: 22, width: 22, height: 22,
left: i * 10, left: i * 10,
@@ -226,16 +226,16 @@ export function ChannelSidebar() {
<div className={`text-[15px] truncate leading-tight ${ <div className={`text-[15px] truncate leading-tight ${
currentChannelId === dm.id ? 'text-white font-medium' currentChannelId === dm.id ? 'text-white font-medium'
: isDmUnread ? 'text-white font-bold' : 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} {dmDisplayName}
</div> </div>
{isGroup ? ( {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 {dm.members.length} Members
</div> </div>
) : dm.lastMessage ? ( ) : 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} {dm.lastMessage.content}
</div> </div>
) : null} ) : null}
@@ -250,7 +250,7 @@ export function ChannelSidebar() {
} }
useServerStore.getState().closeDm(dm.id); 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" title="Close DM"
> >
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"> <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
@@ -261,7 +261,7 @@ export function ChannelSidebar() {
); );
})} })}
{dmChannels.length === 0 && ( {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>
</div> </div>
@@ -274,21 +274,21 @@ export function ChannelSidebar() {
return ( 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 */} {/* Server header */}
<div className="h-12 flex items-center shadow-header z-10 group/header"> <div className="h-12 flex items-center shadow-header z-10 group/header">
<button <button
onClick={() => openModal('serverSettings')} 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> <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-discord-text-muted flex-shrink-0"> <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" /> <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> </svg>
</button> </button>
<button <button
onClick={() => openModal('invite')} 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" title="Invite People"
> >
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"> <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
@@ -302,7 +302,7 @@ export function ChannelSidebar() {
{/* Text Channels */} {/* Text Channels */}
<div> <div>
<div className="flex items-center justify-between px-1 mb-1 group cursor-pointer"> <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"> <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" /> <path d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z" />
</svg> </svg>
@@ -314,7 +314,7 @@ export function ChannelSidebar() {
e.stopPropagation(); e.stopPropagation();
openModal('createChannel'); 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" title="Create Channel"
> >
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
@@ -332,10 +332,10 @@ export function ChannelSidebar() {
onClick={() => handleChannelClick(channel.id)} onClick={() => handleChannelClick(channel.id)}
className={`w-full flex items-center gap-1.5 px-2 h-8 rounded-[4px] group transition-colors ${ className={`w-full flex items-center gap-1.5 px-2 h-8 rounded-[4px] group transition-colors ${
currentChannelId === channel.id currentChannelId === channel.id
? 'bg-discord-modifier-selected text-white' ? 'bg-interactive-selected text-white'
: isUnread : isUnread
? 'text-white hover:text-white hover:bg-discord-modifier-hover' ? 'text-white hover:text-white hover:bg-interactive-hover'
: 'text-discord-text-muted hover:text-discord-text-secondary hover:bg-discord-modifier-hover' : 'text-txt-tertiary hover:text-txt-secondary hover:bg-interactive-hover'
}`} }`}
> >
{isUnread && ( {isUnread && (
@@ -351,7 +351,7 @@ export function ChannelSidebar() {
height="16" height="16"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="currentColor" 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) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
openModal('channelSettings', { channelId: channel.id }); openModal('channelSettings', { channelId: channel.id });
@@ -369,7 +369,7 @@ export function ChannelSidebar() {
{/* Voice Channels */} {/* Voice Channels */}
<div> <div>
<div className="flex items-center justify-between px-1 mb-1 group cursor-pointer"> <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"> <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" /> <path d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z" />
</svg> </svg>
@@ -381,7 +381,7 @@ export function ChannelSidebar() {
e.stopPropagation(); e.stopPropagation();
openModal('createChannel'); 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" title="Create Channel"
> >
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
@@ -562,33 +562,33 @@ function UserAreaPanel({
<div className="relative" ref={panelRef}> <div className="relative" ref={panelRef}>
{/* Input settings panel */} {/* Input settings panel */}
{openPanel === 'input' && ( {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 */} {/* Input Device */}
<div className="relative"> <div className="relative">
<button <button
onClick={() => setShowInputDeviceList(!showInputDeviceList)} 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="min-w-0 flex-1">
<div className="text-[15px] font-semibold text-discord-text-primary text-left">Input Device</div> <div className="text-[15px] font-semibold text-txt-primary text-left">Input Device</div>
<div className="text-[13px] text-discord-text-muted truncate text-left">{selectedInputLabel}</div> <div className="text-[13px] text-txt-tertiary truncate text-left">{selectedInputLabel}</div>
</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" /> <path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" />
</svg> </svg>
</button> </button>
{showInputDeviceList && ( {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 => ( {inputDevices.map(d => (
<button <button
key={d.deviceId} key={d.deviceId}
onClick={() => selectInput(d)} 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 ${ 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-discord-text-primary' : 'text-discord-text-secondary' inputDeviceId === d.deviceId ? 'text-txt-primary' : 'text-txt-secondary'
}`} }`}
> >
{inputDeviceId === d.deviceId && ( {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" /> <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" />
</svg> </svg>
)} )}
@@ -599,11 +599,11 @@ function UserAreaPanel({
)} )}
</div> </div>
<div className="mx-4 border-t border-[#2b2d31]" /> <div className="mx-4 border-t border-border-soft" />
{/* Input Volume */} {/* Input Volume */}
<div className="px-4 py-3"> <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 <input
type="range" type="range"
min={0} min={0}
@@ -613,9 +613,9 @@ function UserAreaPanel({
const vol = Number(e.target.value); const vol = Number(e.target.value);
storeSetInputVolume(vol); 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={{ 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 */} {/* Mic level meter */}
@@ -624,22 +624,22 @@ function UserAreaPanel({
<div <div
key={i} key={i}
className={`flex-1 h-[6px] rounded-[1px] transition-colors duration-75 ${ 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> </div>
<div className="mx-4 border-t border-[#2b2d31]" /> <div className="mx-4 border-t border-border-soft" />
{/* Voice Settings link */} {/* Voice Settings link */}
<button <button
onClick={onSettingsClick} 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> <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-discord-text-muted"> <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" /> <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> </svg>
</button> </button>
@@ -648,33 +648,33 @@ function UserAreaPanel({
{/* Output settings panel */} {/* Output settings panel */}
{openPanel === 'output' && ( {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 */} {/* Output Device */}
<div className="relative"> <div className="relative">
<button <button
onClick={() => setShowOutputDeviceList(!showOutputDeviceList)} 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="min-w-0 flex-1">
<div className="text-[15px] font-semibold text-discord-text-primary text-left">Output Device</div> <div className="text-[15px] font-semibold text-txt-primary text-left">Output Device</div>
<div className="text-[13px] text-discord-text-muted truncate text-left">{selectedOutputLabel}</div> <div className="text-[13px] text-txt-tertiary truncate text-left">{selectedOutputLabel}</div>
</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" /> <path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" />
</svg> </svg>
</button> </button>
{showOutputDeviceList && ( {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 => ( {outputDevices.map(d => (
<button <button
key={d.deviceId} key={d.deviceId}
onClick={() => selectOutput(d)} 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 ${ 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-discord-text-primary' : 'text-discord-text-secondary' outputDeviceId === d.deviceId ? 'text-txt-primary' : 'text-txt-secondary'
}`} }`}
> >
{outputDeviceId === d.deviceId && ( {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" /> <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" />
</svg> </svg>
)} )}
@@ -685,11 +685,11 @@ function UserAreaPanel({
)} )}
</div> </div>
<div className="mx-4 border-t border-[#2b2d31]" /> <div className="mx-4 border-t border-border-soft" />
{/* Output Volume */} {/* Output Volume */}
<div className="px-4 py-3"> <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 <input
type="range" type="range"
min={0} min={0}
@@ -699,21 +699,21 @@ function UserAreaPanel({
const vol = Number(e.target.value); const vol = Number(e.target.value);
storeSetOutputVolume(vol); 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={{ 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>
<div className="mx-4 border-t border-[#2b2d31]" /> <div className="mx-4 border-t border-border-soft" />
{/* Voice Settings link */} {/* Voice Settings link */}
<button <button
onClick={onSettingsClick} 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> <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-discord-text-muted"> <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" /> <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> </svg>
</button> </button>
@@ -723,11 +723,11 @@ function UserAreaPanel({
{/* User area bar */} {/* User area bar */}
<div className="h-[52px] px-2 flex items-center select-none"> <div className="h-[52px] px-2 flex items-center select-none">
{/* Avatar + name */} {/* 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} /> <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="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-[13px] font-semibold text-txt-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-[11px] text-txt-tertiary truncate leading-tight group-hover:text-txt-secondary">@{user.username}</div>
</div> </div>
</div> </div>
@@ -736,8 +736,8 @@ function UserAreaPanel({
{/* Mic */} {/* Mic */}
<button <button
onClick={onMicToggle} onClick={onMicToggle}
className={`w-8 h-8 flex items-center justify-center hover:bg-discord-modifier-hover rounded-l-[4px] transition-colors ${ className={`w-8 h-8 flex items-center justify-center hover:bg-interactive-hover rounded-l-[4px] transition-colors ${
isMuted ? 'text-discord-red' : 'text-discord-text-muted hover:text-discord-text-primary' isMuted ? 'text-txt-danger' : 'text-txt-tertiary hover:text-txt-primary'
}`} }`}
title={isMuted ? 'Unmute' : 'Mute'} title={isMuted ? 'Unmute' : 'Mute'}
> >
@@ -750,8 +750,8 @@ function UserAreaPanel({
{/* Input chevron */} {/* Input chevron */}
<button <button
onClick={() => togglePanel('input')} onClick={() => togglePanel('input')}
className={`w-[18px] h-8 flex items-center justify-center hover:bg-discord-modifier-hover rounded-r-[4px] transition-colors ${ className={`w-[18px] h-8 flex items-center justify-center hover:bg-interactive-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' openPanel === 'input' ? 'text-txt-primary bg-interactive-hover' : 'text-txt-tertiary hover:text-txt-primary'
}`} }`}
title="Input Devices" title="Input Devices"
> >
@@ -763,8 +763,8 @@ function UserAreaPanel({
{/* Headphones */} {/* Headphones */}
<button <button
onClick={onDeafenToggle} onClick={onDeafenToggle}
className={`w-8 h-8 flex items-center justify-center hover:bg-discord-modifier-hover rounded-l-[4px] transition-colors ${ className={`w-8 h-8 flex items-center justify-center hover:bg-interactive-hover rounded-l-[4px] transition-colors ${
isDeafened ? 'text-discord-red' : 'text-discord-text-muted hover:text-discord-text-primary' isDeafened ? 'text-txt-danger' : 'text-txt-tertiary hover:text-txt-primary'
}`} }`}
title={isDeafened ? 'Undeafen' : 'Deafen'} title={isDeafened ? 'Undeafen' : 'Deafen'}
> >
@@ -776,8 +776,8 @@ function UserAreaPanel({
{/* Output chevron */} {/* Output chevron */}
<button <button
onClick={() => togglePanel('output')} onClick={() => togglePanel('output')}
className={`w-[18px] h-8 flex items-center justify-center hover:bg-discord-modifier-hover rounded-r-[4px] transition-colors ${ className={`w-[18px] h-8 flex items-center justify-center hover:bg-interactive-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' openPanel === 'output' ? 'text-txt-primary bg-interactive-hover' : 'text-txt-tertiary hover:text-txt-primary'
}`} }`}
title="Output Devices" title="Output Devices"
> >
@@ -789,7 +789,7 @@ function UserAreaPanel({
{/* Settings */} {/* Settings */}
<button <button
onClick={onSettingsClick} 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" title="User Settings"
> >
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"> <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
@@ -90,23 +90,23 @@ export function MainContent() {
return ( return (
<div <div
ref={voiceContainerRef} 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"> <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" /> <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> </svg>
<span className="font-bold text-discord-text-primary">{dmName}</span> <span className="font-bold text-txt-primary">{dmName}</span>
{connectionError ? ( {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 ? ( ) : isLiveKitConnected ? (
<> <>
<span className="text-xs text-discord-green font-medium ml-2">Connected</span> <span className="text-xs text-status-online 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-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>
<div className="flex items-center gap-1 flex-shrink-0"> <div className="flex items-center gap-1 flex-shrink-0">
@@ -127,38 +127,38 @@ export function MainContent() {
} }
return ( 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 && ( {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"> <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" /> <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> </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> </div>
<button <button
onClick={handleCancelCall} 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 Cancel
</button> </button>
</div> </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"> <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" /> <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> </svg>
<span className="font-bold text-discord-text-primary truncate">{dmName}</span> <span className="font-bold text-txt-primary truncate">{dmName}</span>
{isGroupDm && ( {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>
<div className="flex items-center gap-1 flex-shrink-0"> <div className="flex items-center gap-1 flex-shrink-0">
<button <button
onClick={handleStartVoiceCall} onClick={handleStartVoiceCall}
disabled={!!outgoingCall || !!activeDmCall} 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" title="Start Voice Call"
> >
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"> <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
@@ -168,14 +168,14 @@ export function MainContent() {
<button <button
onClick={handleStartVoiceCall} onClick={handleStartVoiceCall}
disabled={!!outgoingCall || !!activeDmCall} 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" title="Start Video Call"
> >
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"> <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" /> <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> </svg>
</button> </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"> <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="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" /> <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>
<button <button
onClick={() => openModal('addDmMember', { dmChannelId: currentChannelId })} 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" title="Add Friends to DM"
> >
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"> <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
@@ -191,18 +191,18 @@ export function MainContent() {
</svg> </svg>
</button> </button>
<MemberListToggleButton /> <MemberListToggleButton />
<div className="w-[1px] h-6 bg-discord-modifier-accent mx-1" /> <div className="w-[1px] h-6 bg-interactive-muted 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"> <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"> <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" /> <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> </svg>
</button> </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"> <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" /> <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> </svg>
</button> </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"> <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" /> <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> </svg>
@@ -218,14 +218,14 @@ export function MainContent() {
if (!currentChannelId || !channel) { if (!currentChannelId || !channel) {
return ( 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"> <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"> <div className="flex items-center gap-1 flex-shrink-0">
<MemberListToggleButton /> <MemberListToggleButton />
</div> </div>
</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> <p>Select a text or voice channel to get started</p>
</div> </div>
</div> </div>
@@ -237,30 +237,30 @@ export function MainContent() {
if (!isInThisChannel) { if (!isInThisChannel) {
return ( return (
<div className="flex-1 flex flex-col 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-[#0b0c0e]"> <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"> <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" /> <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> </svg>
<span className="font-bold text-discord-text-primary">{channel.name}</span> <span className="font-bold text-txt-primary">{channel.name}</span>
</div> </div>
<div className="flex items-center gap-1 flex-shrink-0"> <div className="flex items-center gap-1 flex-shrink-0">
<MemberListToggleButton /> <MemberListToggleButton />
</div> </div>
</div> </div>
<div className="flex-1 flex flex-col items-center justify-center gap-8 relative"> <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"> <div className="text-center relative z-10">
<h2 className="text-[28px] font-bold text-white mb-3">{channel.name}</h2> <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> </div>
<button <button
onClick={() => { onClick={() => {
useVoiceStore.getState().setCurrentVoiceChannel(currentChannelId); useVoiceStore.getState().setCurrentVoiceChannel(currentChannelId);
wsSend({ type: 'voice_join', channelId: 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 Join Voice
</button> </button>
@@ -272,23 +272,23 @@ export function MainContent() {
return ( return (
<div <div
ref={voiceContainerRef} 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"> <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" /> <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> </svg>
<span className="font-bold text-discord-text-primary">{channel.name}</span> <span className="font-bold text-txt-primary">{channel.name}</span>
{connectionError ? ( {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 ? ( ) : isLiveKitConnected ? (
<> <>
<span className="text-xs text-discord-green font-medium ml-2">Connected</span> <span className="text-xs text-status-online 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-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>
<div className="flex items-center gap-1 flex-shrink-0"> <div className="flex items-center gap-1 flex-shrink-0">
@@ -309,49 +309,49 @@ export function MainContent() {
} }
return ( 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">
<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"> <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" /> <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> </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 && ( {channel.topic && (
<> <>
<div className="w-[1px] h-6 bg-discord-bg-accent mx-2" /> <div className="w-[1px] h-6 bg-interactive-muted mx-2" />
<span className="text-xs text-discord-text-muted truncate leading-tight">{channel.topic}</span> <span className="text-xs text-txt-tertiary truncate leading-tight">{channel.topic}</span>
</> </>
)} )}
</div> </div>
<div className="flex items-center gap-1 flex-shrink-0"> <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"> <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" /> <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> </svg>
</button> </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"> <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" /> <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> </svg>
</button> </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"> <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" /> <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> </svg>
</button> </button>
<MemberListToggleButton /> <MemberListToggleButton />
<div className="w-[1px] h-6 bg-discord-modifier-accent mx-1" /> <div className="w-[1px] h-6 bg-interactive-muted 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"> <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"> <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" /> <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> </svg>
</button> </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"> <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" /> <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> </svg>
</button> </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"> <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" /> <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> </svg>
@@ -8,8 +8,8 @@ export function MemberListToggleButton() {
return ( return (
<button <button
onClick={toggleMemberList} onClick={toggleMemberList}
className={`w-8 h-8 flex items-center justify-center transition-colors rounded-[4px] hover:bg-discord-modifier-hover ${ className={`w-8 h-8 flex items-center justify-center transition-colors rounded-[4px] hover:bg-interactive-hover ${
memberListOpen ? 'text-discord-text-primary' : 'text-discord-text-muted hover:text-discord-text-secondary' memberListOpen ? 'text-txt-primary' : 'text-txt-tertiary hover:text-txt-secondary'
}`} }`}
title="Toggle Member List" title="Toggle Member List"
> >
@@ -15,7 +15,7 @@ function getMemberGroup(member: MemberWithUser, ownerId: string | undefined) {
return { return {
key: '__owner__', key: '__owner__',
label: 'OWNER', label: 'OWNER',
color: ownerRole?.color ?? '#f23f43', color: ownerRole?.color ?? 'var(--accent-rose)',
position: Infinity, position: Infinity,
}; };
} }
@@ -79,7 +79,7 @@ export function MemberSidebar() {
return { color: sorted[0]!.color }; return { color: sorted[0]!.color };
} }
if (ownerId && member.userId === ownerId) { if (ownerId && member.userId === ownerId) {
return { color: '#f23f43' }; return { color: 'var(--accent-rose)' };
} }
return undefined; return undefined;
}; };
@@ -100,7 +100,7 @@ export function MemberSidebar() {
<div <div
key={member.userId} key={member.userId}
onClick={(e) => handleMemberClick(e, member.user)} 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 <Avatar
src={member.user.avatar} src={member.user.avatar}
@@ -111,13 +111,13 @@ export function MemberSidebar() {
/> />
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div <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} style={colorStyle}
> >
{displayName} {displayName}
</div> </div>
{!isOffline && member.user.customStatus && ( {!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>
</div> </div>
@@ -125,12 +125,12 @@ export function MemberSidebar() {
}; };
return ( 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"> <div className="p-3">
{/* Role-based groups */} {/* Role-based groups */}
{roleGroups.map(([key, group]) => ( {roleGroups.map(([key, group]) => (
<div key={key} className="mb-4"> <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} {group.label} {group.members.length}
</h3> </h3>
{group.members.map((m) => renderMember(m))} {group.members.map((m) => renderMember(m))}
@@ -140,7 +140,7 @@ export function MemberSidebar() {
{/* Offline */} {/* Offline */}
{offlineMembers.length > 0 && ( {offlineMembers.length > 0 && (
<div> <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} OFFLINE {offlineMembers.length}
</h3> </h3>
{offlineMembers.map((m) => renderMember(m, true))} {offlineMembers.map((m) => renderMember(m, true))}
@@ -13,7 +13,7 @@ export function MobileNav() {
{/* Hamburger button in header */} {/* Hamburger button in header */}
<button <button
onClick={toggleSidebar} 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"> <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
{sidebarOpen ? ( {sidebarOpen ? (
@@ -27,7 +27,7 @@ export function MobileNav() {
{/* Backdrop when sidebar is open on mobile */} {/* Backdrop when sidebar is open on mobile */}
{sidebarOpen && ( {sidebarOpen && (
<div <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} 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'; const base = 'w-12 h-12 flex items-center justify-center transition-all duration-200 overflow-hidden relative group';
if (type === 'dm') { 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'}`; 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'}`;
} }
if (type === 'action') { 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} bg-surface-chat rounded-[24px] hover:rounded-[16px] text-status-online hover:bg-status-online 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'}`; 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 ( return (
@@ -134,7 +134,7 @@ export function ServerSidebar() {
}; };
return ( 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 <SidebarItem
id="@me" id="@me"
name="Direct Messages" name="Direct Messages"
@@ -144,7 +144,7 @@ export function ServerSidebar() {
hasUnread={hasDmUnread} 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) => ( {servers.map((server) => (
<SidebarItem <SidebarItem
+5 -5
View File
@@ -25,18 +25,18 @@ export function Modal({ isOpen, onClose, title, children, maxWidth = 'max-w-md'
if (!isOpen) return null; if (!isOpen) return null;
return ( 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 <div
className="absolute inset-0 bg-discord-bg-overlay" className="absolute inset-0 bg-surface-overlay"
onClick={onClose} 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 && ( {title && (
<div className="flex items-center justify-between px-4 pt-4"> <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 <button
onClick={onClose} 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"> <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" /> <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 const statusColor = connectionError
? 'text-discord-red' ? 'text-txt-danger'
: isLiveKitConnected : isLiveKitConnected
? 'text-discord-green' ? 'text-status-online'
: 'text-discord-yellow'; : 'text-status-idle';
const statusBgColor = connectionError const statusBgColor = connectionError
? 'bg-discord-red/20' ? 'bg-accent-rose/20'
: isLiveKitConnected : isLiveKitConnected
? 'bg-discord-green/20' ? 'bg-status-online/20'
: 'bg-discord-yellow/20'; : 'bg-status-idle/20';
const qualityColor = const qualityColor =
connectionQuality === 'excellent' || connectionQuality === 'good' connectionQuality === 'excellent' || connectionQuality === 'good'
? 'text-discord-green' ? 'text-status-online'
: connectionQuality === 'poor' : connectionQuality === 'poor'
? 'text-discord-yellow' ? 'text-status-idle'
: connectionQuality === 'lost' : connectionQuality === 'lost'
? 'text-discord-red' ? 'text-txt-danger'
: statusColor; // 'unknown' falls back to connection-state color : statusColor; // 'unknown' falls back to connection-state color
const btnBase = 'flex-1 h-[34px] flex items-center justify-center rounded-[4px] transition-colors'; 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 ( return (
<> <>
@@ -123,7 +123,7 @@ export function VoiceControls() {
<div className={`text-[13px] font-semibold leading-[18px] ${statusColor}`}> <div className={`text-[13px] font-semibold leading-[18px] ${statusColor}`}>
{connectionError ? 'Connection Failed' : isLiveKitConnected ? 'Voice Connected' : 'Connecting...'} {connectionError ? 'Connection Failed' : isLiveKitConnected ? 'Voice Connected' : 'Connecting...'}
</div> </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} {connectionError ? connectionError : channelName}
</div> </div>
</div> </div>
@@ -131,7 +131,7 @@ export function VoiceControls() {
<div className="flex items-center gap-0.5 flex-shrink-0"> <div className="flex items-center gap-0.5 flex-shrink-0">
<button <button
onClick={handleDisconnect} 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" title="Disconnect"
> >
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"> <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
@@ -153,7 +153,7 @@ export function VoiceControls() {
onClick={handleCamera} onClick={handleCamera}
className={`${btnBase} ${ className={`${btnBase} ${
isCameraOn isCameraOn
? 'bg-[#111214] text-discord-green hover:bg-[#1a1b1e]' ? 'bg-surface-base text-status-online hover:bg-surface-channel'
: btnDefaultStyle : btnDefaultStyle
}`} }`}
title={isCameraOn ? 'Turn Off Camera' : 'Turn On Camera'} title={isCameraOn ? 'Turn Off Camera' : 'Turn On Camera'}
@@ -174,7 +174,7 @@ export function VoiceControls() {
onClick={handleScreenShare} onClick={handleScreenShare}
className={`${btnBase} ${ className={`${btnBase} ${
isScreenSharing isScreenSharing
? 'bg-[#111214] text-discord-green hover:bg-[#1a1b1e]' ? 'bg-surface-base text-status-online hover:bg-surface-channel'
: btnDefaultStyle : btnDefaultStyle
}`} }`}
title={isScreenSharing ? 'Stop Sharing' : 'Share Screen'} title={isScreenSharing ? 'Stop Sharing' : 'Share Screen'}
@@ -193,7 +193,7 @@ export function VoiceControls() {
}} }}
className={`${btnBase} ${ className={`${btnBase} ${
showScreenShareSettings showScreenShareSettings
? 'bg-[#111214] text-discord-blurple hover:bg-[#1a1b1e]' ? 'bg-surface-base text-accent-primary hover:bg-surface-channel'
: btnDefaultStyle : btnDefaultStyle
}`} }`}
title="Video Quality" title="Video Quality"
@@ -209,7 +209,7 @@ export function VoiceControls() {
onClick={() => setRnnoiseEnabled(!rnnoiseEnabled)} onClick={() => setRnnoiseEnabled(!rnnoiseEnabled)}
className={`${btnBase} ${ className={`${btnBase} ${
rnnoiseEnabled rnnoiseEnabled
? 'bg-[#111214] text-discord-green hover:bg-[#1a1b1e]' ? 'bg-surface-base text-status-online hover:bg-surface-channel'
: btnDefaultStyle : btnDefaultStyle
}`} }`}
title={rnnoiseEnabled ? 'Disable AI Noise Suppression' : 'Enable AI Noise Suppression'} title={rnnoiseEnabled ? 'Disable AI Noise Suppression' : 'Enable AI Noise Suppression'}
+130 -9
View File
@@ -3,55 +3,176 @@
@tailwind utilities; @tailwind utilities;
@layer base { @layer base {
:root {
/* ── Matte Surfaces ── */
--bg-base: #0b0b10;
--bg-channel: #1a1a23;
--bg-chat: #13131a;
--bg-members: #1a1a23;
--bg-elevated: #252530;
--bg-input: #111118;
--bg-overlay: rgba(0, 0, 0, 0.85);
/* ── Borders ── */
--border-hard: #22222c;
--border-soft: #2a2a36;
/* ── Pastel Accents ── */
--accent-mint: #86efac;
--accent-peach: #fca5a5;
--accent-lavender: #c4b5fd;
--accent-sky: #7dd3fc;
--accent-amber: #fcd34d;
--accent-rose: #fda4af;
--accent-coral: #fb923c;
/* ── Primary Action (replaces blurple) ── */
--accent-primary: #7c6cf6;
--accent-primary-hover: #6b5ce0;
--accent-primary-active: #5f4fd0;
/* ── Text Hierarchy ── */
--text-primary: #efefef;
--text-secondary: #a0a0aa;
--text-tertiary: #5c5c68;
--text-message: #d8d8de;
--text-link: #7dd3fc;
--text-positive: #86efac;
--text-warning: #fcd34d;
--text-danger: #fca5a5;
/* ── Interactive ── */
--interactive-hover: rgba(255, 255, 255, 0.06);
--interactive-active: rgba(255, 255, 255, 0.12);
--interactive-selected: rgba(255, 255, 255, 0.16);
--interactive-muted: #3a3a44;
/* ── Status ── */
--status-online: #86efac;
--status-idle: #fcd34d;
--status-dnd: #fca5a5;
--status-offline: #3a3a44;
/* ── Glass Material ── */
--glass-bg: rgba(20, 20, 26, 0.52);
--glass-border: rgba(255, 255, 255, 0.07);
--glass-highlight: rgba(255, 255, 255, 0.05);
/* ── Notification ── */
--notification: #fda4af;
}
* { * {
@apply border-none; @apply border-none;
outline: none !important; outline: none !important;
} }
::selection { ::selection {
background: rgba(88, 101, 242, 0.3); background: rgba(134, 239, 172, 0.25);
color: var(--text-primary);
} }
html, body, #root { html, body, #root {
@apply h-full w-full overflow-hidden bg-discord-bg-tertiary text-discord-text-normal; @apply h-full w-full overflow-hidden bg-surface-base text-txt-message;
font-family: 'Inter', 'gg sans', 'Noto Sans', sans-serif; font-family: 'Inter', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
} }
/* Discord-style thin scrollbar */ /* Thin scrollbar */
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 8px; width: 8px;
height: 8px; height: 8px;
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
@apply bg-transparent; background: transparent;
} }
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
@apply bg-[#111214] rounded-full border-2 border-transparent bg-clip-padding; background-color: var(--bg-input);
border-radius: 9999px;
border: 2px solid transparent;
background-clip: padding-box;
} }
::-webkit-scrollbar-thumb:hover { ::-webkit-scrollbar-thumb:hover {
@apply bg-[#1a1b1e]; background-color: var(--bg-channel);
} }
.no-scrollbar::-webkit-scrollbar { .no-scrollbar::-webkit-scrollbar {
display: none; display: none;
} }
/* For dark theme auto-fill */ /* Dark theme auto-fill */
input:-webkit-autofill, input:-webkit-autofill,
input:-webkit-autofill:hover, input:-webkit-autofill:hover,
input:-webkit-autofill:focus { input:-webkit-autofill:focus {
-webkit-text-fill-color: #ffffff; -webkit-text-fill-color: #ffffff;
-webkit-box-shadow: 0 0 0px 1000px #111214 inset; -webkit-box-shadow: 0 0 0px 1000px #111118 inset;
transition: background-color 5000s ease-in-out 0s; transition: background-color 5000s ease-in-out 0s;
} }
} }
@layer components {
/* Standard glass — popovers, floating elements */
.glass {
backdrop-filter: blur(20px) saturate(120%);
-webkit-backdrop-filter: blur(20px) saturate(120%);
background: var(--glass-bg);
border: 1px solid var(--glass-border);
box-shadow:
0 2px 8px rgba(0, 0, 0, 0.25),
0 8px 24px rgba(0, 0, 0, 0.15),
inset 0 1px 0 var(--glass-highlight);
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
/* Server strip glass — no border-radius, rightward shadow */
.glass-strip {
backdrop-filter: blur(20px) saturate(120%);
-webkit-backdrop-filter: blur(20px) saturate(120%);
background: var(--glass-bg);
border-right: 1px solid rgba(255, 255, 255, 0.05);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.03),
2px 0 12px rgba(0, 0, 0, 0.20);
}
/* Floating bubble glass — bottom bar, input pill */
.glass-bubble {
backdrop-filter: blur(20px) saturate(120%);
-webkit-backdrop-filter: blur(20px) saturate(120%);
background: var(--glass-bg);
border: 1px solid var(--glass-border);
box-shadow:
0 2px 8px rgba(0, 0, 0, 0.20),
0 8px 24px rgba(0, 0, 0, 0.12),
inset 0 1px 0 var(--glass-highlight);
}
}
/* Accessibility: fall back to solid surfaces when transparency is reduced */
@media (prefers-reduced-transparency: reduce) {
.glass {
backdrop-filter: none;
-webkit-backdrop-filter: none;
background: var(--bg-channel);
}
.glass-strip {
backdrop-filter: none;
-webkit-backdrop-filter: none;
background: var(--bg-channel);
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.20);
}
.glass-bubble {
backdrop-filter: none;
-webkit-backdrop-filter: none;
background: var(--bg-elevated);
}
}
@layer utilities { @layer utilities {
.rounded-inherit { .rounded-inherit {
border-radius: inherit; border-radius: inherit;
+58 -1
View File
@@ -7,6 +7,7 @@ export default {
theme: { theme: {
extend: { extend: {
colors: { colors: {
// ── LEGACY (preserved until Phase 5 removal) ──
discord: { discord: {
// Backgrounds (darkest → lightest) — Discord "Dark" theme // Backgrounds (darkest → lightest) — Discord "Dark" theme
'bg-floating': '#080a0b', 'bg-floating': '#080a0b',
@@ -60,14 +61,70 @@ export default {
'modifier-selected': 'rgba(255,255,255,0.20)', 'modifier-selected': 'rgba(255,255,255,0.20)',
'modifier-accent': 'rgba(255,255,255,0.12)', 'modifier-accent': 'rgba(255,255,255,0.12)',
}, },
// ── AETHER DRIFT TOKENS ──
surface: {
base: 'var(--bg-base)',
channel: 'var(--bg-channel)',
chat: 'var(--bg-chat)',
members: 'var(--bg-members)',
elevated: 'var(--bg-elevated)',
input: 'var(--bg-input)',
overlay: 'var(--bg-overlay)',
},
border: {
hard: 'var(--border-hard)',
soft: 'var(--border-soft)',
},
accent: {
mint: 'var(--accent-mint)',
peach: 'var(--accent-peach)',
lavender: 'var(--accent-lavender)',
sky: 'var(--accent-sky)',
amber: 'var(--accent-amber)',
rose: 'var(--accent-rose)',
coral: 'var(--accent-coral)',
primary: 'var(--accent-primary)',
'primary-hover': 'var(--accent-primary-hover)',
'primary-active':'var(--accent-primary-active)',
},
txt: {
primary: 'var(--text-primary)',
secondary: 'var(--text-secondary)',
tertiary: 'var(--text-tertiary)',
message: 'var(--text-message)',
link: 'var(--text-link)',
positive: 'var(--text-positive)',
warning: 'var(--text-warning)',
danger: 'var(--text-danger)',
},
interactive: {
hover: 'var(--interactive-hover)',
active: 'var(--interactive-active)',
selected: 'var(--interactive-selected)',
muted: 'var(--interactive-muted)',
},
status: {
online: 'var(--status-online)',
idle: 'var(--status-idle)',
dnd: 'var(--status-dnd)',
offline: 'var(--status-offline)',
},
glass: {
bg: 'var(--glass-bg)',
border: 'var(--glass-border)',
highlight: 'var(--glass-highlight)',
},
notification: 'var(--notification)',
}, },
boxShadow: { boxShadow: {
'header': '0 1px 0 rgba(4, 4, 5, 0.2), 0 1.5px 0 rgba(6, 6, 7, 0.05), 0 2px 0 rgba(4, 4, 5, 0.05)', 'header': '0 1px 0 rgba(4, 4, 5, 0.2), 0 1.5px 0 rgba(6, 6, 7, 0.05), 0 2px 0 rgba(4, 4, 5, 0.05)',
'elevation-low': '0 1px 0 rgba(4, 4, 5, 0.2), 0 1.5px 0 rgba(6, 6, 7, 0.05), 0 2px 0 rgba(4, 4, 5, 0.05)', 'elevation-low': '0 1px 0 rgba(4, 4, 5, 0.2), 0 1.5px 0 rgba(6, 6, 7, 0.05), 0 2px 0 rgba(4, 4, 5, 0.05)',
'elevation-high': '0 8px 16px rgba(0, 0, 0, 0.24)', 'elevation-high': '0 8px 16px rgba(0, 0, 0, 0.24)',
'glass': '0 2px 8px rgba(0,0,0,0.25), 0 8px 24px rgba(0,0,0,0.15), inset 0 1px 0 var(--glass-highlight)',
}, },
fontFamily: { fontFamily: {
sans: ['"gg sans"', 'Inter', 'Noto Sans', 'Helvetica Neue', 'Helvetica', 'Arial', 'sans-serif'], sans: ['Inter', '-apple-system', 'BlinkMacSystemFont', 'system-ui', 'sans-serif'],
}, },
}, },
}, },