feat: Phase 4 Aether Drift — auth, modals & UI component token migration

Migrate all auth pages, modals, and shared UI components from Discord
visual language to Aether Drift design tokens. Zero discord-* classes
or raw Tailwind palette colors remain in components/auth/, modals/, ui/.

- Avatar: status dots use status-online/idle/dnd/offline tokens
- Tooltip, ContextMenu, UserProfilePopout: z-index raised to z-[200]
- LoginPage, RegisterPage: warm bg-surface-base with lavender glow
- All modals: inputs use surface-input, buttons use accent-primary
- Toggles: bg-status-online (on), bg-surface-input (off)
- ServerSettings: slider tracks, pills, danger zone fully tokenized
- Purged raw Tailwind green-500 leak in StreamingLimitsPanel success msg
This commit is contained in:
Jannis Braun
2026-03-02 00:01:24 +01:00
parent 7172091159
commit d0e696e03c
15 changed files with 173 additions and 173 deletions
+15 -15
View File
@@ -32,44 +32,44 @@ export function LoginPage() {
};
return (
<div className="min-h-screen flex items-center justify-center bg-[#080a0b] relative">
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,rgba(88,101,242,0.06)_0%,transparent_50%)]" />
<div className="w-full max-w-[480px] bg-discord-bg-surface rounded-md p-8 shadow-elevation-high relative z-10">
<div className="min-h-screen flex items-center justify-center bg-surface-base relative">
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,rgba(124,108,246,0.06)_0%,transparent_50%)]" />
<div className="w-full max-w-[480px] bg-surface-elevated rounded-md p-8 shadow-elevation-high relative z-10">
<div className="text-center mb-6">
<h1 className="text-2xl font-bold text-discord-text-primary">Welcome back!</h1>
<p className="text-discord-text-muted mt-1">We're so excited to see you again!</p>
<h1 className="text-2xl font-bold text-txt-primary">Welcome back!</h1>
<p className="text-txt-tertiary mt-1">We're so excited to see you again!</p>
</div>
<form onSubmit={handleSubmit}>
{error && (
<div className="mb-4 p-3 bg-discord-red/10 border border-discord-red/30 rounded text-discord-text-danger text-sm">
<div className="mb-4 p-3 bg-accent-rose/10 border border-accent-rose/30 rounded text-txt-danger text-sm">
{error}
</div>
)}
<div className="mb-5">
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
Username <span className="text-discord-red">*</span>
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
Username <span className="text-txt-danger">*</span>
</label>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="w-full px-3 py-2.5 bg-discord-bg-tertiary border-none rounded text-discord-text-primary outline-none focus:ring-2 focus:ring-discord-blurple transition-all"
className="w-full px-3 py-2.5 bg-surface-input border-none rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary transition-all"
autoFocus
autoComplete="username"
/>
</div>
<div className="mb-5">
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
Password <span className="text-discord-red">*</span>
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
Password <span className="text-txt-danger">*</span>
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-3 py-2.5 bg-discord-bg-tertiary border-none rounded text-discord-text-primary outline-none focus:ring-2 focus:ring-discord-blurple transition-all"
className="w-full px-3 py-2.5 bg-surface-input border-none rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary transition-all"
autoComplete="current-password"
/>
</div>
@@ -77,14 +77,14 @@ export function LoginPage() {
<button
type="submit"
disabled={isLoading}
className="w-full py-2.5 bg-discord-blurple hover:bg-discord-blurple-hover text-white font-medium rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
className="w-full py-2.5 bg-accent-primary hover:bg-accent-primary/80 text-white font-medium rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{isLoading ? 'Logging in...' : 'Log In'}
</button>
<p className="mt-3 text-sm text-discord-text-muted">
<p className="mt-3 text-sm text-txt-tertiary">
Need an account?{' '}
<Link to="/register" className="text-discord-text-link hover:underline">
<Link to="/register" className="text-accent-primary hover:underline">
Register
</Link>
</p>
@@ -45,56 +45,56 @@ export function RegisterPage() {
};
return (
<div className="min-h-screen flex items-center justify-center bg-[#080a0b] relative">
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,rgba(88,101,242,0.06)_0%,transparent_50%)]" />
<div className="w-full max-w-[480px] bg-discord-bg-surface rounded-md p-8 shadow-elevation-high relative z-10">
<div className="min-h-screen flex items-center justify-center bg-surface-base relative">
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,rgba(124,108,246,0.06)_0%,transparent_50%)]" />
<div className="w-full max-w-[480px] bg-surface-elevated rounded-md p-8 shadow-elevation-high relative z-10">
<div className="text-center mb-6">
<h1 className="text-2xl font-bold text-discord-text-primary">Create an account</h1>
<h1 className="text-2xl font-bold text-txt-primary">Create an account</h1>
</div>
<form onSubmit={handleSubmit}>
{error && (
<div className="mb-4 p-3 bg-discord-red/10 border border-discord-red/30 rounded text-discord-text-danger text-sm">
<div className="mb-4 p-3 bg-accent-rose/10 border border-accent-rose/30 rounded text-txt-danger text-sm">
{error}
</div>
)}
<div className="mb-5">
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
Username <span className="text-discord-red">*</span>
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
Username <span className="text-txt-danger">*</span>
</label>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="w-full px-3 py-2.5 bg-discord-bg-tertiary border-none rounded text-discord-text-primary outline-none focus:ring-2 focus:ring-discord-blurple transition-all"
className="w-full px-3 py-2.5 bg-surface-input border-none rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary transition-all"
autoFocus
autoComplete="username"
/>
</div>
<div className="mb-5">
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
Display Name
</label>
<input
type="text"
value={displayName}
onChange={(e) => setDisplayName(e.target.value)}
className="w-full px-3 py-2.5 bg-discord-bg-tertiary border-none rounded text-discord-text-primary outline-none focus:ring-2 focus:ring-discord-blurple transition-all"
className="w-full px-3 py-2.5 bg-surface-input border-none rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary transition-all"
autoComplete="name"
/>
</div>
<div className="mb-5">
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
Password <span className="text-discord-red">*</span>
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
Password <span className="text-txt-danger">*</span>
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-3 py-2.5 bg-discord-bg-tertiary border-none rounded text-discord-text-primary outline-none focus:ring-2 focus:ring-discord-blurple transition-all"
className="w-full px-3 py-2.5 bg-surface-input border-none rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary transition-all"
autoComplete="new-password"
/>
</div>
@@ -102,14 +102,14 @@ export function RegisterPage() {
<button
type="submit"
disabled={isLoading}
className="w-full py-2.5 bg-discord-blurple hover:bg-discord-blurple-hover text-white font-medium rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
className="w-full py-2.5 bg-accent-primary hover:bg-accent-primary/80 text-white font-medium rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{isLoading ? 'Creating account...' : 'Continue'}
</button>
<p className="mt-3 text-sm text-discord-text-muted">
<p className="mt-3 text-sm text-txt-tertiary">
Already have an account?{' '}
<Link to="/login" className="text-discord-text-link hover:underline">
<Link to="/login" className="text-accent-primary hover:underline">
Log In
</Link>
</p>
@@ -80,10 +80,10 @@ export function AddDmMemberModal() {
<Modal isOpen={isOpen} onClose={closeModal} title="Add Friends to DM">
<div className="space-y-3">
<div className="flex items-center justify-between">
<p className="text-[13px] text-discord-text-muted">
<p className="text-[13px] text-txt-tertiary">
Search for a user to add to this conversation.
</p>
<span className="text-[12px] text-discord-text-muted flex-shrink-0 ml-2">
<span className="text-[12px] text-txt-tertiary flex-shrink-0 ml-2">
{memberCount}/10
</span>
</div>
@@ -94,25 +94,25 @@ export function AddDmMemberModal() {
value={query}
onChange={(e) => handleSearch(e.target.value)}
placeholder="Search for a user..."
className="w-full px-3 py-2 bg-discord-bg-tertiary text-discord-text-primary placeholder-discord-text-muted/60 rounded-[4px] text-[14px] outline-none focus:ring-1 focus:ring-discord-blurple"
className="w-full px-3 py-2 bg-surface-input text-txt-primary placeholder-txt-tertiary/60 rounded-[4px] text-[14px] outline-none focus:ring-1 focus:ring-accent-primary"
disabled={memberCount >= 10}
/>
{memberCount >= 10 && (
<p className="text-discord-red text-[13px]">This group DM has reached the 10-member limit.</p>
<p className="text-txt-danger text-[13px]">This group DM has reached the 10-member limit.</p>
)}
{error && (
<p className="text-discord-red text-[13px]">{error}</p>
<p className="text-txt-danger text-[13px]">{error}</p>
)}
<div className="max-h-[300px] overflow-y-auto space-y-[2px]">
{isSearching && (
<div className="py-4 text-center text-discord-text-muted text-[14px]">Searching...</div>
<div className="py-4 text-center text-txt-tertiary text-[14px]">Searching...</div>
)}
{!isSearching && query.trim().length >= 2 && results.length === 0 && (
<div className="py-4 text-center text-discord-text-muted text-[14px]">No users found</div>
<div className="py-4 text-center text-txt-tertiary text-[14px]">No users found</div>
)}
{results.map((user) => (
@@ -120,14 +120,14 @@ export function AddDmMemberModal() {
key={user.id}
onClick={() => handleSelectUser(user)}
disabled={isAdding}
className="w-full flex items-center gap-3 px-3 py-2 rounded-[4px] hover:bg-discord-modifier-hover transition-colors text-left disabled:opacity-50"
className="w-full flex items-center gap-3 px-3 py-2 rounded-[4px] hover:bg-interactive-hover transition-colors text-left disabled:opacity-50"
>
<Avatar src={user.avatar} name={user.displayName ?? user.username} size={36} status={user.status as any} />
<div className="flex-1 min-w-0">
<div className="text-[14px] font-medium text-discord-text-primary truncate">
<div className="text-[14px] font-medium text-txt-primary truncate">
{user.displayName ?? user.username}
</div>
<div className="text-[12px] text-discord-text-muted truncate">@{user.username}</div>
<div className="text-[12px] text-txt-tertiary truncate">@{user.username}</div>
</div>
</button>
))}
@@ -92,10 +92,10 @@ export function ChannelSettingsModal() {
<Modal isOpen={isOpen} onClose={closeModal} title="Channel Settings" maxWidth="max-w-md">
<div className="space-y-4">
<div>
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
Channel
</label>
<div className="flex items-center gap-2 text-discord-text-primary">
<div className="flex items-center gap-2 text-txt-primary">
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" className="opacity-60 flex-shrink-0">
<path d="M5.88657 21C5.57547 21 5.3399 20.7189 5.39427 20.4126L6.00001 17H2.59511C2.28449 17 2.04905 16.7198 2.10259 16.4138L2.27759 15.4138C2.31946 15.1746 2.52722 15 2.77011 15H6.35001L7.41001 9H4.00511C3.69449 9 3.45905 8.71977 3.51259 8.41381L3.68759 7.41381C3.72946 7.17456 3.93722 7 4.18011 7H7.76001L8.39677 3.41262C8.43914 3.17391 8.64664 3 8.88907 3H9.87344C10.1845 3 10.4201 3.28107 10.3657 3.58738L9.76001 7H15.76L16.3968 3.41262C16.4391 3.17391 16.6466 3 16.8891 3H17.8734C18.1845 3 18.4201 3.28107 18.3657 3.58738L17.76 7H21.1649C21.4755 7 21.711 7.28023 21.6574 7.58619L21.4824 8.58619C21.4406 8.82544 21.2328 9 20.9899 9H17.41L16.35 15H19.7549C20.0655 15 20.301 15.2802 20.2474 15.5862L20.0724 16.5862C20.0306 16.8254 19.8228 17 19.5799 17H16L15.3632 20.5874C15.3209 20.8261 15.1134 21 14.8709 21H13.8866C13.5755 21 13.3399 20.7189 13.3943 20.4126L14 17H8.00001L7.36325 20.5874C7.32088 20.8261 7.11337 21 6.87094 21H5.88657ZM9.41001 9L8.35001 15H14.35L15.41 9H9.41001Z" />
</svg>
@@ -104,16 +104,16 @@ export function ChannelSettingsModal() {
</div>
{error && (
<div className="p-2 bg-discord-red/10 border border-discord-red/30 rounded text-discord-text-danger text-sm">
<div className="p-2 bg-accent-rose/10 border border-accent-rose/30 rounded text-txt-danger text-sm">
{error}
</div>
)}
<div className="pt-2 border-t border-discord-bg-tertiary">
<div className="pt-2 border-t border-border-soft">
<div className="flex items-center justify-between">
<div>
<div className="text-sm font-medium text-discord-text-primary">Private Channel</div>
<div className="text-xs text-discord-text-muted mt-0.5">
<div className="text-sm font-medium text-txt-primary">Private Channel</div>
<div className="text-xs text-txt-tertiary mt-0.5">
Only selected members and roles will be able to view this channel.
</div>
</div>
@@ -122,10 +122,10 @@ export function ChannelSettingsModal() {
disabled={isLoading || isFetching}
className={`relative w-10 h-6 rounded-full transition-colors flex-shrink-0 ml-4 ${
isFetching
? 'bg-discord-bg-tertiary opacity-50 cursor-wait'
? 'bg-surface-input opacity-50 cursor-wait'
: isPrivate
? 'bg-discord-green'
: 'bg-discord-bg-tertiary'
? 'bg-status-online'
: 'bg-surface-input'
} ${isLoading ? 'opacity-70 cursor-wait' : 'cursor-pointer'}`}
aria-label={isPrivate ? 'Make channel public' : 'Make channel private'}
>
@@ -139,8 +139,8 @@ export function ChannelSettingsModal() {
</div>
{isPrivate && !isFetching && (
<div className="flex items-start gap-2 p-2 bg-discord-bg-tertiary/50 rounded text-xs text-discord-text-muted">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" className="flex-shrink-0 mt-0.5 text-discord-text-secondary">
<div className="flex items-start gap-2 p-2 bg-surface-input/50 rounded text-xs text-txt-tertiary">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" className="flex-shrink-0 mt-0.5 text-txt-secondary">
<path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z" />
</svg>
<span>
@@ -48,13 +48,13 @@ export function CreateChannelModal() {
<Modal isOpen={isOpen} onClose={closeModal} title="Create Channel">
<form onSubmit={handleSubmit}>
{error && (
<div className="mb-3 p-2 bg-discord-red/10 border border-discord-red/30 rounded text-discord-text-danger text-sm">
<div className="mb-3 p-2 bg-accent-rose/10 border border-accent-rose/30 rounded text-txt-danger text-sm">
{error}
</div>
)}
<div className="mb-4">
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
Channel Type
</label>
<div className="space-y-2">
@@ -63,8 +63,8 @@ export function CreateChannelModal() {
key={t}
className={`flex items-center gap-3 p-3 rounded cursor-pointer border ${
type === t
? 'border-discord-blurple bg-discord-bg-hover'
: 'border-discord-bg-tertiary bg-discord-bg-secondary hover:bg-discord-bg-hover'
? 'border-accent-primary bg-interactive-hover'
: 'border-border-soft bg-surface-channel hover:bg-interactive-hover'
}`}
>
<input
@@ -75,7 +75,7 @@ export function CreateChannelModal() {
onChange={() => setType(t)}
className="hidden"
/>
<div className="w-5 h-5 text-discord-text-muted">
<div className="w-5 h-5 text-txt-tertiary">
{t === 'text' && (
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M5.88657 21C5.57547 21 5.3399 20.7189 5.39427 20.4126L6.00001 17H2.59511C2.28449 17 2.04905 16.7198 2.10259 16.4138L2.27759 15.4138C2.31946 15.1746 2.52722 15 2.77011 15H6.35001L7.41001 9H4.00511C3.69449 9 3.45905 8.71977 3.51259 8.41381L3.68759 7.41381C3.72946 7.17456 3.93722 7 4.18011 7H7.76001L8.39677 3.41262C8.43914 3.17391 8.64664 3 8.88907 3H9.87344C10.1845 3 10.4201 3.28107 10.3657 3.58738L9.76001 7H15.76L16.3968 3.41262C16.4391 3.17391 16.6466 3 16.8891 3H17.8734C18.1845 3 18.4201 3.28107 18.3657 3.58738L17.76 7H21.1649C21.4755 7 21.711 7.28023 21.6574 7.58619L21.4824 8.58619C21.4406 8.82544 21.2328 9 20.9899 9H17.41L16.35 15H19.7549C20.0655 15 20.301 15.2802 20.2474 15.5862L20.0724 16.5862C20.0306 16.8254 19.8228 17 19.5799 17H16L15.3632 20.5874C15.3209 20.8261 15.1134 21 14.8709 21H13.8866C13.5755 21 13.3399 20.7189 13.3943 20.4126L14 17H8.00001L7.36325 20.5874C7.32088 20.8261 7.11337 21 6.87094 21H5.88657ZM9.41001 9L8.35001 15H14.35L15.41 9H9.41001Z" /></svg>
)}
@@ -87,8 +87,8 @@ export function CreateChannelModal() {
)}
</div>
<div>
<div className="text-sm font-medium text-discord-text-primary capitalize">{t}</div>
<div className="text-xs text-discord-text-muted">
<div className="text-sm font-medium text-txt-primary capitalize">{t}</div>
<div className="text-xs text-txt-tertiary">
{t === 'text' && 'Send messages, images, and files'}
{t === 'voice' && 'Hang out with voice and video'}
{t === 'video' && 'Share your screen and camera'}
@@ -100,14 +100,14 @@ export function CreateChannelModal() {
</div>
<div className="mb-4">
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
Channel Name
</label>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full px-3 py-2 bg-discord-bg-tertiary rounded text-discord-text-primary outline-none focus:ring-2 focus:ring-discord-blurple"
className="w-full px-3 py-2 bg-surface-input rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary"
placeholder="new-channel"
autoFocus
/>
@@ -115,14 +115,14 @@ export function CreateChannelModal() {
{type === 'text' && (
<div className="mb-4">
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
Topic (optional)
</label>
<input
type="text"
value={topic}
onChange={(e) => setTopic(e.target.value)}
className="w-full px-3 py-2 bg-discord-bg-tertiary rounded text-discord-text-primary outline-none focus:ring-2 focus:ring-discord-blurple"
className="w-full px-3 py-2 bg-surface-input rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary"
placeholder="What's this channel about?"
/>
</div>
@@ -132,14 +132,14 @@ export function CreateChannelModal() {
<button
type="button"
onClick={closeModal}
className="px-4 py-2 text-sm text-discord-text-secondary hover:text-discord-text-primary transition-colors"
className="px-4 py-2 text-sm text-txt-secondary hover:text-txt-primary transition-colors"
>
Cancel
</button>
<button
type="submit"
disabled={isLoading}
className="px-4 py-2 bg-discord-blurple hover:bg-discord-blurple-hover text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
className="px-4 py-2 bg-accent-primary hover:bg-accent-primary/80 text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
>
{isLoading ? 'Creating...' : 'Create Channel'}
</button>
@@ -41,19 +41,19 @@ export function CreateServerModal() {
<Modal isOpen={isOpen} onClose={closeModal} title="Create a Server">
<form onSubmit={handleSubmit}>
{error && (
<div className="mb-3 p-2 bg-discord-red/10 border border-discord-red/30 rounded text-discord-text-danger text-sm">
<div className="mb-3 p-2 bg-accent-rose/10 border border-accent-rose/30 rounded text-txt-danger text-sm">
{error}
</div>
)}
<div className="mb-4">
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
Server Name
</label>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full px-3 py-2 bg-discord-bg-tertiary rounded text-discord-text-primary outline-none focus:ring-2 focus:ring-discord-blurple"
className="w-full px-3 py-2 bg-surface-input rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary"
placeholder="My Awesome Server"
autoFocus
/>
@@ -62,14 +62,14 @@ export function CreateServerModal() {
<button
type="button"
onClick={closeModal}
className="px-4 py-2 text-sm text-discord-text-secondary hover:text-discord-text-primary transition-colors"
className="px-4 py-2 text-sm text-txt-secondary hover:text-txt-primary transition-colors"
>
Cancel
</button>
<button
type="submit"
disabled={isLoading}
className="px-4 py-2 bg-discord-blurple hover:bg-discord-blurple-hover text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
className="px-4 py-2 bg-accent-primary hover:bg-accent-primary/80 text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
>
{isLoading ? 'Creating...' : 'Create'}
</button>
@@ -51,11 +51,11 @@ export function InviteModal() {
return (
<Modal isOpen={isOpen} onClose={closeModal} title="Invite Friends">
<p className="text-discord-text-secondary text-sm mb-4">
<p className="text-txt-secondary text-sm mb-4">
Share this invite link with friends to let them join your server.
</p>
{error && (
<div className="mb-3 p-2 bg-discord-red/10 border border-discord-red/30 rounded text-discord-text-danger text-sm">
<div className="mb-3 p-2 bg-accent-rose/10 border border-accent-rose/30 rounded text-txt-danger text-sm">
{error}
</div>
)}
@@ -64,15 +64,15 @@ export function InviteModal() {
type="text"
value={isLoading ? 'Generating...' : inviteUrl}
readOnly
className="invite-code-input flex-1 px-3 py-2 bg-discord-bg-tertiary rounded text-discord-text-primary outline-none font-mono text-xs"
className="invite-code-input flex-1 px-3 py-2 bg-surface-input rounded text-txt-primary outline-none font-mono text-xs"
/>
<button
onClick={handleCopy}
disabled={isLoading || !inviteUrl}
className={`px-4 py-2 text-sm font-medium rounded transition-colors ${
copied
? 'bg-discord-green text-white'
: 'bg-discord-blurple hover:bg-discord-blurple-hover text-white'
? 'bg-status-online text-white'
: 'bg-accent-primary hover:bg-accent-primary/80 text-white'
}`}
>
{copied ? 'Copied!' : 'Copy'}
@@ -48,23 +48,23 @@ export function JoinServerModal() {
return (
<Modal isOpen={isOpen} onClose={closeModal} title="Join a Server">
<form onSubmit={handleSubmit}>
<p className="text-discord-text-secondary text-sm mb-4">
<p className="text-txt-secondary text-sm mb-4">
Enter an invite code to join an existing server.
</p>
{error && (
<div className="mb-3 p-2 bg-discord-red/10 border border-discord-red/30 rounded text-discord-text-danger text-sm">
<div className="mb-3 p-2 bg-accent-rose/10 border border-accent-rose/30 rounded text-txt-danger text-sm">
{error}
</div>
)}
<div className="mb-4">
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
Invite Code
</label>
<input
type="text"
value={inviteCode}
onChange={(e) => setInviteCode(e.target.value)}
className="w-full px-3 py-2 bg-discord-bg-tertiary rounded text-discord-text-primary outline-none focus:ring-2 focus:ring-discord-blurple"
className="w-full px-3 py-2 bg-surface-input rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary"
placeholder="e.g. abc123"
autoFocus
/>
@@ -73,14 +73,14 @@ export function JoinServerModal() {
<button
type="button"
onClick={closeModal}
className="px-4 py-2 text-sm text-discord-text-secondary hover:text-discord-text-primary transition-colors"
className="px-4 py-2 text-sm text-txt-secondary hover:text-txt-primary transition-colors"
>
Cancel
</button>
<button
type="submit"
disabled={isLoading}
className="px-4 py-2 bg-discord-blurple hover:bg-discord-blurple-hover text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
className="px-4 py-2 bg-accent-primary hover:bg-accent-primary/80 text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
>
{isLoading ? 'Joining...' : 'Join Server'}
</button>
@@ -78,34 +78,34 @@ export function NewDmModal() {
value={query}
onChange={(e) => handleSearch(e.target.value)}
placeholder="Search for a user..."
className="w-full px-3 py-2 bg-discord-bg-tertiary text-discord-text-primary placeholder-discord-text-muted/60 rounded-[4px] text-[14px] outline-none focus:ring-1 focus:ring-discord-blurple"
className="w-full px-3 py-2 bg-surface-input text-txt-primary placeholder-txt-tertiary/60 rounded-[4px] text-[14px] outline-none focus:ring-1 focus:ring-accent-primary"
/>
{error && (
<p className="text-discord-red text-[13px]">{error}</p>
<p className="text-txt-danger text-[13px]">{error}</p>
)}
<div className="max-h-[300px] overflow-y-auto space-y-[2px]">
{isSearching && (
<div className="py-4 text-center text-discord-text-muted text-[14px]">Searching...</div>
<div className="py-4 text-center text-txt-tertiary text-[14px]">Searching...</div>
)}
{!isSearching && query.trim().length >= 2 && results.length === 0 && (
<div className="py-4 text-center text-discord-text-muted text-[14px]">No users found</div>
<div className="py-4 text-center text-txt-tertiary text-[14px]">No users found</div>
)}
{results.map((user) => (
<button
key={user.id}
onClick={() => handleSelectUser(user)}
className="w-full flex items-center gap-3 px-3 py-2 rounded-[4px] hover:bg-discord-modifier-hover transition-colors text-left"
className="w-full flex items-center gap-3 px-3 py-2 rounded-[4px] hover:bg-interactive-hover transition-colors text-left"
>
<Avatar src={user.avatar} name={user.displayName ?? user.username} size={36} status={user.status as any} />
<div className="flex-1 min-w-0">
<div className="text-[14px] font-medium text-discord-text-primary truncate">
<div className="text-[14px] font-medium text-txt-primary truncate">
{user.displayName ?? user.username}
</div>
<div className="text-[12px] text-discord-text-muted truncate">@{user.username}</div>
<div className="text-[12px] text-txt-tertiary truncate">@{user.username}</div>
</div>
</button>
))}
@@ -32,7 +32,7 @@ function StreamingLimitsPanel() {
if (limits) setDraft({ ...limits });
}, [limits]);
if (!draft) return <div className="text-sm text-discord-text-muted">Loading settings...</div>;
if (!draft) return <div className="text-sm text-txt-tertiary">Loading settings...</div>;
const hasChanges = JSON.stringify(draft) !== JSON.stringify(limits);
@@ -79,23 +79,23 @@ function StreamingLimitsPanel() {
};
const pillBase = 'px-3 py-1.5 rounded text-[13px] font-medium transition-colors cursor-pointer select-none';
const pillOn = 'bg-discord-blurple text-white';
const pillOff = 'bg-[#2b2d31] text-discord-text-secondary hover:bg-[#35373c]';
const pillOn = 'bg-accent-primary text-white';
const pillOff = 'bg-surface-elevated text-txt-secondary hover:bg-interactive-hover';
return (
<div className="space-y-4">
<div className="text-xs text-discord-text-muted">
<div className="text-xs text-txt-tertiary">
These limits apply to all users on this instance. Users can pick values within these bounds.
</div>
{/* Bitrate Range */}
<div>
<div className="text-[11px] text-discord-text-muted font-semibold uppercase tracking-wider mb-2">
<div className="text-[11px] text-txt-tertiary font-semibold uppercase tracking-wider mb-2">
Bitrate Range
</div>
<div className="flex items-center gap-3">
<div className="flex-1">
<label className="text-[11px] text-discord-text-muted mb-1 block">Min</label>
<label className="text-[11px] text-txt-tertiary mb-1 block">Min</label>
<input
type="range"
min={100}
@@ -103,15 +103,15 @@ function StreamingLimitsPanel() {
step={100}
value={draft.minBitrateKbps}
onChange={(e) => setDraft({ ...draft, minBitrateKbps: Number(e.target.value) })}
className="w-full h-1.5 accent-discord-blurple cursor-pointer appearance-none bg-[#4e5058] rounded-full
className="w-full h-1.5 accent-accent-primary cursor-pointer appearance-none bg-interactive-muted rounded-full
[&::-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
[&::-webkit-slider-thumb]:cursor-pointer [&::-webkit-slider-thumb]:border-0"
/>
<div className="text-[11px] text-discord-text-secondary mt-0.5">{formatKbps(draft.minBitrateKbps)}</div>
<div className="text-[11px] text-txt-secondary mt-0.5">{formatKbps(draft.minBitrateKbps)}</div>
</div>
<div className="flex-1">
<label className="text-[11px] text-discord-text-muted mb-1 block">Max</label>
<label className="text-[11px] text-txt-tertiary mb-1 block">Max</label>
<input
type="range"
min={draft.minBitrateKbps + 500}
@@ -119,19 +119,19 @@ function StreamingLimitsPanel() {
step={500}
value={draft.maxBitrateKbps}
onChange={(e) => setDraft({ ...draft, maxBitrateKbps: Number(e.target.value) })}
className="w-full h-1.5 accent-discord-blurple cursor-pointer appearance-none bg-[#4e5058] rounded-full
className="w-full h-1.5 accent-accent-primary cursor-pointer appearance-none bg-interactive-muted rounded-full
[&::-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
[&::-webkit-slider-thumb]:cursor-pointer [&::-webkit-slider-thumb]:border-0"
/>
<div className="text-[11px] text-discord-text-secondary mt-0.5">{formatKbps(draft.maxBitrateKbps)}</div>
<div className="text-[11px] text-txt-secondary mt-0.5">{formatKbps(draft.maxBitrateKbps)}</div>
</div>
</div>
</div>
{/* Bitrate Step */}
<div>
<div className="text-[11px] text-discord-text-muted font-semibold uppercase tracking-wider mb-1.5">
<div className="text-[11px] text-txt-tertiary font-semibold uppercase tracking-wider mb-1.5">
Slider Step
</div>
<div className="flex items-center gap-2">
@@ -145,15 +145,15 @@ function StreamingLimitsPanel() {
const v = Number(e.target.value);
if (v >= 50 && v <= 5000) setDraft({ ...draft, bitrateStepKbps: v });
}}
className="w-24 px-2 py-1 bg-discord-bg-tertiary rounded text-sm text-discord-text-primary outline-none focus:ring-1 focus:ring-discord-blurple"
className="w-24 px-2 py-1 bg-surface-input rounded text-sm text-txt-primary outline-none focus:ring-1 focus:ring-accent-primary"
/>
<span className="text-[12px] text-discord-text-muted">kbps</span>
<span className="text-[12px] text-txt-tertiary">kbps</span>
</div>
</div>
{/* Allowed Resolutions */}
<div>
<div className="text-[11px] text-discord-text-muted font-semibold uppercase tracking-wider mb-1.5">
<div className="text-[11px] text-txt-tertiary font-semibold uppercase tracking-wider mb-1.5">
Allowed Resolutions
</div>
<div className="flex gap-1.5">
@@ -171,7 +171,7 @@ function StreamingLimitsPanel() {
{/* Allowed Frame Rates */}
<div>
<div className="text-[11px] text-discord-text-muted font-semibold uppercase tracking-wider mb-1.5">
<div className="text-[11px] text-txt-tertiary font-semibold uppercase tracking-wider mb-1.5">
Allowed Frame Rates
</div>
<div className="flex gap-1.5">
@@ -189,23 +189,23 @@ function StreamingLimitsPanel() {
{/* Save / Reset */}
{saveError && (
<div className="p-2 bg-discord-red/10 border border-discord-red/30 rounded text-discord-text-danger text-sm">{saveError}</div>
<div className="p-2 bg-accent-rose/10 border border-accent-rose/30 rounded text-txt-danger text-sm">{saveError}</div>
)}
{saveSuccess && (
<div className="p-2 bg-green-500/10 border border-green-500/30 rounded text-green-400 text-sm">Settings saved</div>
<div className="p-2 bg-status-online/10 border border-status-online/30 rounded text-status-online text-sm">Settings saved</div>
)}
{hasChanges && (
<div className="flex items-center gap-2">
<button
onClick={handleSave}
disabled={saving}
className="px-4 py-1.5 bg-discord-blurple hover:bg-discord-blurple-hover text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
className="px-4 py-1.5 bg-accent-primary hover:bg-accent-primary/80 text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
>
{saving ? 'Saving...' : 'Save'}
</button>
<button
onClick={handleReset}
className="px-4 py-1.5 text-sm text-discord-text-muted hover:text-discord-text-secondary transition-colors"
className="px-4 py-1.5 text-sm text-txt-tertiary hover:text-txt-secondary transition-colors"
>
Reset
</button>
@@ -341,7 +341,7 @@ export function ServerSettingsModal() {
<button
onClick={() => setTab('overview')}
className={`w-full text-left px-3 py-1.5 rounded text-sm transition-colors ${
tab === 'overview' ? 'bg-discord-bg-active text-discord-text-primary' : 'text-discord-text-muted hover:text-discord-text-secondary hover:bg-discord-bg-hover'
tab === 'overview' ? 'bg-interactive-selected text-txt-primary' : 'text-txt-tertiary hover:text-txt-secondary hover:bg-interactive-hover'
}`}
>
Overview
@@ -349,7 +349,7 @@ export function ServerSettingsModal() {
<button
onClick={() => setTab('members')}
className={`w-full text-left px-3 py-1.5 rounded text-sm transition-colors ${
tab === 'members' ? 'bg-discord-bg-active text-discord-text-primary' : 'text-discord-text-muted hover:text-discord-text-secondary hover:bg-discord-bg-hover'
tab === 'members' ? 'bg-interactive-selected text-txt-primary' : 'text-txt-tertiary hover:text-txt-secondary hover:bg-interactive-hover'
}`}
>
Members
@@ -358,7 +358,7 @@ export function ServerSettingsModal() {
<button
onClick={() => setTab('streaming')}
className={`w-full text-left px-3 py-1.5 rounded text-sm transition-colors ${
tab === 'streaming' ? 'bg-discord-bg-active text-discord-text-primary' : 'text-discord-text-muted hover:text-discord-text-secondary hover:bg-discord-bg-hover'
tab === 'streaming' ? 'bg-interactive-selected text-txt-primary' : 'text-txt-tertiary hover:text-txt-secondary hover:bg-interactive-hover'
}`}
>
Streaming
@@ -369,20 +369,20 @@ export function ServerSettingsModal() {
{/* Content */}
<div className="flex-1 min-w-0">
{error && (
<div className="mb-3 p-2 bg-discord-red/10 border border-discord-red/30 rounded text-discord-text-danger text-sm">{error}</div>
<div className="mb-3 p-2 bg-accent-rose/10 border border-accent-rose/30 rounded text-txt-danger text-sm">{error}</div>
)}
{tab === 'overview' && (
<div className="space-y-4">
<div>
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
Server Name
</label>
<input
type="text"
value={serverName}
onChange={(e) => setServerName(e.target.value)}
className="w-full px-3 py-2 bg-discord-bg-tertiary rounded text-discord-text-primary outline-none focus:ring-2 focus:ring-discord-blurple"
className="w-full px-3 py-2 bg-surface-input rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary"
disabled={!canManageServer}
/>
</div>
@@ -392,16 +392,16 @@ export function ServerSettingsModal() {
<button
onClick={handleSave}
disabled={isLoading}
className="px-4 py-2 bg-discord-blurple hover:bg-discord-blurple-hover text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
className="px-4 py-2 bg-accent-primary hover:bg-accent-primary/80 text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
>
{isLoading ? 'Saving...' : 'Save Changes'}
</button>
<div className="pt-4 border-t border-discord-bg-tertiary">
<h3 className="text-sm font-bold text-discord-red mb-2">Danger Zone</h3>
<div className="pt-4 border-t border-border-soft">
<h3 className="text-sm font-bold text-txt-danger mb-2">Danger Zone</h3>
<button
onClick={handleDelete}
className="px-4 py-2 bg-discord-red hover:bg-discord-red-hover text-white text-sm font-medium rounded transition-colors"
className="px-4 py-2 bg-accent-rose hover:bg-accent-rose/80 text-white text-sm font-medium rounded transition-colors"
>
{confirmDelete ? 'Click again to confirm deletion' : 'Delete Server'}
</button>
@@ -420,7 +420,7 @@ export function ServerSettingsModal() {
const hasPendingChanges = pendingRoleChanges.has(member.userId);
return (
<div key={member.userId} className="p-2 rounded hover:bg-discord-bg-hover">
<div key={member.userId} className="p-2 rounded hover:bg-interactive-hover">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Avatar
@@ -433,7 +433,7 @@ export function ServerSettingsModal() {
<div className="text-sm font-medium">{displayName}</div>
<div className="flex items-center gap-1 flex-wrap">
{isOwner && (
<span className="text-[10px] px-1.5 py-0.5 rounded bg-discord-red/20 text-discord-red font-medium">
<span className="text-[10px] px-1.5 py-0.5 rounded bg-accent-rose/20 text-txt-danger font-medium">
Owner
</span>
)}
@@ -447,7 +447,7 @@ export function ServerSettingsModal() {
</span>
))}
{!isOwner && (!member.roles || member.roles.filter(r => r.id !== currentServerId).length === 0) && (
<span className="text-[10px] text-discord-text-muted">No roles</span>
<span className="text-[10px] text-txt-tertiary">No roles</span>
)}
</div>
</div>
@@ -457,7 +457,7 @@ export function ServerSettingsModal() {
<div className="flex items-center gap-2">
<button
onClick={() => handleKick(member.userId)}
className="px-2 py-1 text-xs text-discord-red hover:bg-discord-red/10 rounded transition-colors"
className="px-2 py-1 text-xs text-txt-danger hover:bg-accent-rose/10 rounded transition-colors"
>
Kick
</button>
@@ -474,11 +474,11 @@ export function ServerSettingsModal() {
type="checkbox"
checked={memberRoleIds.has(role.id)}
onChange={() => handleRoleToggle(member.userId, role.id, memberRoleIds)}
className="w-3.5 h-3.5 rounded border-discord-text-muted accent-discord-blurple"
className="w-3.5 h-3.5 rounded border-txt-tertiary accent-accent-primary"
/>
<span
className="text-xs font-medium"
style={{ color: role.color !== '#b9bbbe' ? role.color : undefined }}
style={{ color: role.color !== '#9ca3af' ? role.color : undefined }}
>
{role.name}
</span>
@@ -488,13 +488,13 @@ export function ServerSettingsModal() {
<div className="flex items-center gap-2 mt-1.5">
<button
onClick={() => handleSaveRoles(member.userId)}
className="px-2 py-0.5 text-xs bg-discord-blurple hover:bg-discord-blurple-hover text-white rounded transition-colors"
className="px-2 py-0.5 text-xs bg-accent-primary hover:bg-accent-primary/80 text-white rounded transition-colors"
>
Save
</button>
<button
onClick={() => handleCancelRoleChange(member.userId)}
className="px-2 py-0.5 text-xs text-discord-text-muted hover:text-discord-text-secondary transition-colors"
className="px-2 py-0.5 text-xs text-txt-tertiary hover:text-txt-secondary transition-colors"
>
Cancel
</button>
@@ -58,7 +58,7 @@ export function UserSettingsModal() {
<Modal isOpen={isOpen} onClose={closeModal} title="User Settings" maxWidth="max-w-lg">
<div className="space-y-6">
{/* Profile preview */}
<div className="flex items-center gap-4 p-4 bg-discord-bg-secondary rounded-lg">
<div className="flex items-center gap-4 p-4 bg-surface-channel rounded-lg">
<Avatar
src={user.avatar}
name={user.displayName ?? user.username}
@@ -67,28 +67,28 @@ export function UserSettingsModal() {
/>
<div>
<div className="font-bold text-lg">{user.displayName ?? user.username}</div>
<div className="text-discord-text-muted text-sm">@{user.username}</div>
<div className="text-txt-tertiary text-sm">@{user.username}</div>
{user.customStatus && (
<div className="text-discord-text-secondary text-sm mt-1">{user.customStatus}</div>
<div className="text-txt-secondary text-sm mt-1">{user.customStatus}</div>
)}
</div>
</div>
{error && (
<div className="p-2 bg-discord-red/10 border border-discord-red/30 rounded text-discord-text-danger text-sm">{error}</div>
<div className="p-2 bg-accent-rose/10 border border-accent-rose/30 rounded text-txt-danger text-sm">{error}</div>
)}
{success && (
<div className="p-2 bg-discord-green/10 border border-discord-green/30 rounded text-discord-text-positive text-sm">{success}</div>
<div className="p-2 bg-status-online/10 border border-status-online/30 rounded text-status-online text-sm">{success}</div>
)}
<div>
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
Status
</label>
<select
value={status}
onChange={(e) => setStatus(e.target.value as any)}
className="w-full px-3 py-2 bg-discord-bg-tertiary rounded text-discord-text-primary outline-none focus:ring-2 focus:ring-discord-blurple appearance-none"
className="w-full px-3 py-2 bg-surface-input rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary appearance-none"
>
<option value="online">Online</option>
<option value="idle">Idle</option>
@@ -97,44 +97,44 @@ export function UserSettingsModal() {
</div>
<div>
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
Display Name
</label>
<input
type="text"
value={displayName}
onChange={(e) => setDisplayName(e.target.value)}
className="w-full px-3 py-2 bg-discord-bg-tertiary rounded text-discord-text-primary outline-none focus:ring-2 focus:ring-discord-blurple"
className="w-full px-3 py-2 bg-surface-input rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary"
/>
</div>
<div>
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
Custom Status
</label>
<input
type="text"
value={customStatus}
onChange={(e) => setCustomStatus(e.target.value)}
className="w-full px-3 py-2 bg-discord-bg-tertiary rounded text-discord-text-primary outline-none focus:ring-2 focus:ring-discord-blurple"
className="w-full px-3 py-2 bg-surface-input rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary"
placeholder="What are you up to?"
/>
</div>
{/* Voice Processing */}
<div className="border-t border-white/[0.06] pt-4">
<h3 className="text-xs font-bold text-discord-text-secondary uppercase mb-3">
<h3 className="text-xs font-bold text-txt-secondary uppercase mb-3">
Voice Processing
</h3>
<div className="flex items-center justify-between py-2">
<div>
<div className="text-sm text-discord-text-primary">AI Noise Suppression</div>
<div className="text-xs text-discord-text-muted">ML-based noise removal (RNNoise) filters keyboard, fans, and background noise</div>
<div className="text-sm text-txt-primary">AI Noise Suppression</div>
<div className="text-xs text-txt-tertiary">ML-based noise removal (RNNoise) filters keyboard, fans, and background noise</div>
</div>
<button
onClick={() => setRnnoiseEnabled(!rnnoiseEnabled)}
className={`relative w-10 h-5 rounded-full transition-colors ${rnnoiseEnabled ? 'bg-discord-green' : 'bg-discord-bg-tertiary'}`}
className={`relative w-10 h-5 rounded-full transition-colors ${rnnoiseEnabled ? 'bg-status-online' : 'bg-surface-input'}`}
>
<div className={`absolute top-0.5 left-0.5 w-4 h-4 rounded-full bg-white transition-transform ${rnnoiseEnabled ? 'translate-x-5' : 'translate-x-0'}`} />
</button>
@@ -142,12 +142,12 @@ export function UserSettingsModal() {
<div className="flex items-center justify-between py-2">
<div>
<div className="text-sm text-discord-text-primary">Echo Cancellation</div>
<div className="text-xs text-discord-text-muted">Removes echo when using speakers (auto-disabled during screen share)</div>
<div className="text-sm text-txt-primary">Echo Cancellation</div>
<div className="text-xs text-txt-tertiary">Removes echo when using speakers (auto-disabled during screen share)</div>
</div>
<button
onClick={() => setEchoCancellation(!echoCancellation)}
className={`relative w-10 h-5 rounded-full transition-colors ${echoCancellation ? 'bg-discord-green' : 'bg-discord-bg-tertiary'}`}
className={`relative w-10 h-5 rounded-full transition-colors ${echoCancellation ? 'bg-status-online' : 'bg-surface-input'}`}
>
<div className={`absolute top-0.5 left-0.5 w-4 h-4 rounded-full bg-white transition-transform ${echoCancellation ? 'translate-x-5' : 'translate-x-0'}`} />
</button>
@@ -155,12 +155,12 @@ export function UserSettingsModal() {
<div className="flex items-center justify-between py-2">
<div>
<div className="text-sm text-discord-text-primary">Auto Gain Control</div>
<div className="text-xs text-discord-text-muted">Auto-adjusts mic volume can cause voice ducking during streams</div>
<div className="text-sm text-txt-primary">Auto Gain Control</div>
<div className="text-xs text-txt-tertiary">Auto-adjusts mic volume can cause voice ducking during streams</div>
</div>
<button
onClick={() => setAutoGainControl(!autoGainControl)}
className={`relative w-10 h-5 rounded-full transition-colors ${autoGainControl ? 'bg-discord-green' : 'bg-discord-bg-tertiary'}`}
className={`relative w-10 h-5 rounded-full transition-colors ${autoGainControl ? 'bg-status-online' : 'bg-surface-input'}`}
>
<div className={`absolute top-0.5 left-0.5 w-4 h-4 rounded-full bg-white transition-transform ${autoGainControl ? 'translate-x-5' : 'translate-x-0'}`} />
</button>
@@ -170,14 +170,14 @@ export function UserSettingsModal() {
<div className="flex items-center justify-between pt-2">
<button
onClick={handleLogout}
className="px-4 py-2 text-sm text-discord-red hover:bg-discord-red/10 rounded transition-colors"
className="px-4 py-2 text-sm text-txt-danger hover:bg-accent-rose/10 rounded transition-colors"
>
Log Out
</button>
<button
onClick={handleSave}
disabled={isLoading}
className="px-4 py-2 bg-discord-blurple hover:bg-discord-blurple-hover text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
className="px-4 py-2 bg-accent-primary hover:bg-accent-primary/80 text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
>
{isLoading ? 'Saving...' : 'Save Changes'}
</button>
+6 -6
View File
@@ -13,10 +13,10 @@ interface AvatarProps {
}
const statusColors: Record<string, string> = {
online: 'bg-discord-green',
idle: 'bg-discord-yellow',
dnd: 'bg-discord-red',
offline: 'bg-discord-text-muted',
online: 'bg-status-online',
idle: 'bg-status-idle',
dnd: 'bg-status-dnd',
offline: 'bg-status-offline',
};
export function Avatar({ src, name, size = 40, status, className = '', onClick, user }: AvatarProps) {
@@ -59,14 +59,14 @@ export function Avatar({ src, name, size = 40, status, className = '', onClick,
/>
) : null}
<div
className={`avatar-fallback w-full h-full rounded-full bg-discord-blurple flex items-center justify-center ${fontSize} font-semibold text-white ${src ? 'hidden' : 'flex'}`}
className={`avatar-fallback w-full h-full rounded-full bg-accent-primary flex items-center justify-center ${fontSize} font-semibold text-white ${src ? 'hidden' : 'flex'}`}
style={src ? { display: 'none' } : undefined}
>
{initials}
</div>
{status && (
<div
className={`absolute -bottom-0.5 -right-0.5 rounded-full border-[3px] border-discord-bg-secondary ${statusColors[status] ?? 'bg-discord-text-muted'}`}
className={`absolute -bottom-0.5 -right-0.5 rounded-full border-[3px] border-surface-channel ${statusColors[status] ?? 'bg-status-offline'}`}
style={{
width: size * 0.35,
height: size * 0.35,
@@ -62,7 +62,7 @@ export function ContextMenu({ items, children }: ContextMenuProps) {
{isOpen && (
<div
ref={menuRef}
className="fixed z-50 min-w-[180px] py-1.5 bg-discord-bg-floating rounded-md shadow-elevation-high animate-fade-in"
className="fixed z-[200] min-w-[180px] py-1.5 bg-surface-elevated rounded-md shadow-elevation-high animate-fade-in"
style={{ left: position.x, top: position.y }}
>
{items.map((item, i) => (
@@ -70,8 +70,8 @@ export function ContextMenu({ items, children }: ContextMenuProps) {
key={i}
className={`w-full text-left px-2 py-1.5 mx-1.5 text-sm rounded-sm flex items-center gap-2 ${
item.danger
? 'text-discord-red hover:bg-discord-red hover:text-white'
: 'text-discord-text-secondary hover:bg-discord-blurple hover:text-white'
? 'text-txt-danger hover:bg-accent-rose hover:text-white'
: 'text-txt-secondary hover:bg-accent-primary hover:text-white'
}`}
style={{ width: 'calc(100% - 12px)' }}
onClick={(e) => {
+1 -1
View File
@@ -38,7 +38,7 @@ export function Tooltip({ content, children, position = 'right', delay = 200 }:
{children}
{isVisible && (
<div
className={`absolute z-50 px-3 py-1.5 text-sm font-medium text-discord-text-primary bg-discord-bg-floating rounded-md shadow-elevation-high whitespace-nowrap pointer-events-none ${positionClasses[position]}`}
className={`absolute z-[200] px-3 py-1.5 text-sm font-medium text-txt-primary bg-surface-elevated rounded-md shadow-elevation-high whitespace-nowrap pointer-events-none ${positionClasses[position]}`}
>
{content}
</div>
@@ -31,15 +31,15 @@ export function UserProfilePopout({ user, onClose, position }: UserProfilePopout
return (
<div
className="fixed z-50 w-[300px] bg-discord-bg-floating rounded-[8px] shadow-elevation-high overflow-hidden animate-fade-in select-none"
className="fixed z-[200] w-[300px] bg-surface-elevated rounded-[8px] shadow-elevation-high overflow-hidden animate-fade-in select-none"
style={position ? { top: position.top, left: position.left } : { top: '50%', left: '50%', transform: 'translate(-50%, -50%)' }}
>
{/* Banner */}
<div className="h-[60px] bg-discord-blurple" />
<div className="h-[60px] bg-accent-primary" />
{/* Avatar Container */}
<div className="px-4 pb-4 relative">
<div className="absolute -top-8 left-4 rounded-full border-[6px] border-discord-bg-floating bg-discord-bg-floating">
<div className="absolute -top-8 left-4 rounded-full border-[6px] border-surface-elevated bg-surface-elevated">
<Avatar
src={user.avatar}
name={displayName}
@@ -49,27 +49,27 @@ export function UserProfilePopout({ user, onClose, position }: UserProfilePopout
</div>
{/* Content */}
<div className="mt-12 bg-discord-bg-tertiary rounded-[8px] p-3">
<div className="text-[20px] font-bold text-discord-text-header leading-tight mb-1">
<div className="mt-12 bg-surface-input rounded-[8px] p-3">
<div className="text-[20px] font-bold text-txt-primary leading-tight mb-1">
{displayName}
</div>
<div className="text-[14px] text-discord-text-normal font-medium mb-3">
<div className="text-[14px] text-txt-secondary font-medium mb-3">
@{user.username}
</div>
<div className="w-full h-[1px] bg-discord-modifier-accent mb-3" />
<div className="w-full h-[1px] bg-border-soft mb-3" />
<div className="mb-3">
<div className="text-[12px] font-bold text-discord-text-header uppercase mb-1">Backspace Member Since</div>
<div className="text-[12px] text-discord-text-normal font-medium">
<div className="text-[12px] font-bold text-txt-primary uppercase mb-1">Backspace Member Since</div>
<div className="text-[12px] text-txt-secondary font-medium">
{new Date(user.createdAt).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}
</div>
</div>
{user.customStatus && (
<div className="mb-3">
<div className="text-[12px] font-bold text-discord-text-header uppercase mb-1">Status</div>
<div className="text-[14px] text-discord-text-normal">{user.customStatus}</div>
<div className="text-[12px] font-bold text-txt-primary uppercase mb-1">Status</div>
<div className="text-[14px] text-txt-secondary">{user.customStatus}</div>
</div>
)}
</div>
@@ -79,7 +79,7 @@ export function UserProfilePopout({ user, onClose, position }: UserProfilePopout
<div className="px-4 pb-4">
<button
onClick={handleSendMessage}
className="w-full py-2 bg-discord-blurple hover:bg-discord-blurple-hover text-white text-[14px] font-medium rounded-[4px] transition-colors"
className="w-full py-2 bg-accent-primary hover:bg-accent-primary/80 text-white text-[14px] font-medium rounded-[4px] transition-colors"
>
Send Message
</button>