Очистка мусора

This commit is contained in:
Халимов Рустам
2026-03-15 21:36:49 +03:00
parent c14999d319
commit 893c9f2316
424 changed files with 689 additions and 493340 deletions
+15 -15
View File
@@ -1587,11 +1587,11 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
>
{/* Avatar */}
<div className="relative">
<div className="absolute inset-0 rounded-full bg-vortex-500/30 animate-call-wave" />
<div className="absolute inset-0 rounded-full bg-knot-500/30 animate-call-wave" />
{displayAvatar ? (
<img src={displayAvatar} alt="" className="relative w-10 h-10 rounded-full object-cover" />
) : (
<div className="relative w-10 h-10 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white font-bold text-sm">
<div className="relative w-10 h-10 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white font-bold text-sm">
{initials}
</div>
)}
@@ -1633,7 +1633,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
>
{/* Ambient background glow for call modal */}
<div className="absolute inset-0 pointer-events-none opacity-40">
<div className="absolute top-[10%] left-[20%] w-[50vh] h-[50vh] bg-vortex-500/30 rounded-full blur-[120px] animate-float" />
<div className="absolute top-[10%] left-[20%] w-[50vh] h-[50vh] bg-knot-500/30 rounded-full blur-[120px] animate-float" />
<div className="absolute bottom-[10%] right-[20%] w-[50vh] h-[50vh] bg-emerald-500/20 rounded-full blur-[120px] animate-float-delayed" />
</div>
@@ -1657,7 +1657,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
key={mic.deviceId}
onClick={() => switchMicrophone(mic.deviceId)}
className={`w-full text-left px-4 py-2.5 text-sm transition-colors ${activeMicId === mic.deviceId
? 'text-vortex-400 bg-vortex-500/20 font-medium'
? 'text-knot-400 bg-knot-500/20 font-medium'
: 'text-zinc-200 hover:bg-zinc-700'
}`}
>
@@ -1679,7 +1679,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
key={cam.deviceId}
onClick={() => switchCamera(cam.deviceId)}
className={`w-full text-left px-4 py-2.5 text-sm transition-colors ${activeCameraId === cam.deviceId
? 'text-vortex-400 bg-vortex-500/20 font-medium'
? 'text-knot-400 bg-knot-500/20 font-medium'
: 'text-zinc-200 hover:bg-zinc-700'
}`}
>
@@ -1704,7 +1704,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
step="0.05"
value={remoteVolume}
onChange={(e) => handleVolumeChange(parseFloat(e.target.value))}
className="w-full h-1.5 rounded-full appearance-none bg-zinc-600 accent-vortex-500 cursor-pointer"
className="w-full h-1.5 rounded-full appearance-none bg-zinc-600 accent-knot-500 cursor-pointer"
/>
<span className="text-xs text-zinc-300 w-8 text-right">{Math.round(remoteVolume * 100)}%</span>
</div>
@@ -1731,11 +1731,11 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
{!hasRemoteVideo && (
<div className="absolute inset-0 flex flex-col items-center justify-center bg-zinc-900 z-10">
<div className="relative mb-6">
<div className="absolute inset-0 rounded-full bg-vortex-500/20 animate-ping" />
<div className="absolute inset-0 rounded-full bg-knot-500/20 animate-ping" />
{displayAvatar ? (
<img src={displayAvatar} alt="" className="relative w-24 h-24 sm:w-32 sm:h-32 rounded-full object-cover border-4 border-vortex-500/30" />
<img src={displayAvatar} alt="" className="relative w-24 h-24 sm:w-32 sm:h-32 rounded-full object-cover border-4 border-knot-500/30" />
) : (
<div className="relative w-24 h-24 sm:w-32 sm:h-32 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white text-3xl sm:text-4xl font-bold border-4 border-vortex-500/30">
<div className="relative w-24 h-24 sm:w-32 sm:h-32 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white text-3xl sm:text-4xl font-bold border-4 border-knot-500/30">
{initials}
</div>
)}
@@ -1743,7 +1743,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
<VideoOff size={48} className="text-zinc-500 mb-2" />
<span className="text-xl text-white font-medium mb-1">{displayName}</span>
<div className="flex items-center gap-2 text-zinc-500">
<div className="w-2 h-2 rounded-full bg-vortex-500 animate-pulse" />
<div className="w-2 h-2 rounded-full bg-knot-500 animate-pulse" />
<span className="text-sm animate-pulse">
{isScreenSharing ? 'Вы транслируете экран...' : 'Ожидание потока...'}
</span>
@@ -1775,7 +1775,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
});
}
}}
className="px-6 py-3 rounded-full bg-vortex-500 hover:bg-vortex-600 text-white font-bold shadow-2xl transition-all hover:scale-105 flex items-center gap-2"
className="px-6 py-3 rounded-full bg-knot-500 hover:bg-knot-600 text-white font-bold shadow-2xl transition-all hover:scale-105 flex items-center gap-2"
>
<Volume2 size={24} />
Включить звук и видео
@@ -1837,8 +1837,8 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
<div className="relative mb-8 mt-4">
{(callState === 'calling' || callState === 'connected') && (
<>
<div className="absolute inset-0 rounded-full bg-vortex-500/30 animate-call-wave" />
<div className="absolute inset-0 rounded-full bg-vortex-500/20 animate-call-wave-delayed" />
<div className="absolute inset-0 rounded-full bg-knot-500/30 animate-call-wave" />
<div className="absolute inset-0 rounded-full bg-knot-500/20 animate-call-wave-delayed" />
</>
)}
{callState === 'incoming' && (
@@ -1851,7 +1851,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
{displayAvatar ? (
<img src={displayAvatar} alt="" className="w-24 h-24 sm:w-32 sm:h-32 rounded-full object-cover shadow-inner" />
) : (
<div className="w-24 h-24 sm:w-32 sm:h-32 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white font-bold text-3xl sm:text-4xl shadow-inner">
<div className="w-24 h-24 sm:w-32 sm:h-32 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white font-bold text-3xl sm:text-4xl shadow-inner">
{initials}
</div>
)}
@@ -1954,7 +1954,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
remoteScreenSharing
? 'bg-zinc-800 text-zinc-600 cursor-not-allowed hidden sm:flex'
: isScreenSharing
? 'bg-vortex-500/30 text-vortex-400'
? 'bg-knot-500/30 text-knot-400'
: 'bg-white/10 text-white hover:bg-white/20'
}`}
title={remoteScreenSharing ? (t('remoteSharingScreen' as any) as string) : isScreenSharing ? t('stopScreenShare') : t('screenShare')}
+3 -3
View File
@@ -147,7 +147,7 @@ function ChatListItem({ chat, isActive }: ChatListItemProps) {
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between">
<div className="flex items-center gap-1.5 min-w-0">
{isPinned && <Pin size={12} className="text-vortex-400 flex-shrink-0 rotate-45" />}
{isPinned && <Pin size={12} className="text-knot-400 flex-shrink-0 rotate-45" />}
<span className="text-sm font-medium text-white truncate">{chatName}</span>
</div>
{timeStr && <span className="text-xs text-zinc-500 flex-shrink-0 ml-2">{timeStr}</span>}
@@ -157,13 +157,13 @@ function ChatListItem({ chat, isActive }: ChatListItemProps) {
{isMine && lastMessage && !lastMessage.isDeleted && (
<span className="flex-shrink-0">
{isRead ? (
<CheckCheck size={14} className="text-vortex-400" />
<CheckCheck size={14} className="text-knot-400" />
) : (
<Check size={14} className="text-zinc-500" />
)}
</span>
)}
<p className={`text-xs truncate ${isTyping ? 'text-vortex-400 font-medium' : draft ? 'text-red-400' : 'text-zinc-400'}`}>
<p className={`text-xs truncate ${isTyping ? 'text-knot-400 font-medium' : draft ? 'text-red-400' : 'text-zinc-400'}`}>
{isTyping ? t('typing') : draft ? <><span className="font-medium">{t('draft')} </span>{stripMarkdown(draft)}</> : previewText}
</p>
</div>
+13 -13
View File
@@ -345,7 +345,7 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
transition={{ duration: 0.6, delay: 0.1, ease: [0.16, 1, 0.3, 1] }}
className="text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-accent via-fuchsia-400 to-indigo-400 mb-4 drop-shadow-lg tracking-tight"
>
SelfHost Messenger
Knot Messenger
</motion.h2>
<motion.p
initial={{ y: 20, opacity: 0 }}
@@ -743,8 +743,8 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
const el = document.getElementById(`msg-${msg.id}`);
if (el) {
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
el.classList.add('bg-vortex-500/20');
setTimeout(() => el.classList.remove('bg-vortex-500/20'), 2000);
el.classList.add('bg-knot-500/20');
setTimeout(() => el.classList.remove('bg-knot-500/20'), 2000);
}
setShowSearch(false);
setSearchText('');
@@ -752,7 +752,7 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
}}
>
<div className="flex items-center gap-2 mb-0.5">
<span className="text-xs font-medium text-vortex-400">
<span className="text-xs font-medium text-knot-400">
{msg.sender?.displayName || msg.sender?.username}
</span>
<span className="text-xs text-zinc-600">
@@ -790,15 +790,15 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
const el = document.getElementById(`msg-${pinnedMsg.id}`);
if (el) {
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
el.classList.add('bg-vortex-500/20');
setTimeout(() => el.classList.remove('bg-vortex-500/20'), 2000);
el.classList.add('bg-knot-500/20');
setTimeout(() => el.classList.remove('bg-knot-500/20'), 2000);
}
}}
className="flex items-center gap-3 px-4 py-2 border-b border-border bg-surface-secondary/60 hover:bg-surface-hover transition-colors text-left w-full flex-shrink-0"
>
<Pin size={16} className="text-vortex-400 flex-shrink-0 rotate-45" />
<Pin size={16} className="text-knot-400 flex-shrink-0 rotate-45" />
<div className="min-w-0 flex-1">
<p className="text-xs font-medium text-vortex-400">{t('pinnedMessage')}</p>
<p className="text-xs font-medium text-knot-400">{t('pinnedMessage')}</p>
<p className="text-sm text-zinc-300 truncate">
{pinnedMsg.content || (pinnedMsg.media?.length > 0 ? t('media') : '...')}
</p>
@@ -825,7 +825,7 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
>
{isLoadingMessages ? (
<div className="flex justify-center py-8">
<div className="w-6 h-6 border-2 border-vortex-500 border-t-transparent rounded-full animate-spin" />
<div className="w-6 h-6 border-2 border-knot-500 border-t-transparent rounded-full animate-spin" />
</div>
) : chatMessages.length === 0 ? (
<div className="flex items-center justify-center h-full">
@@ -923,8 +923,8 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
const el = document.getElementById(`msg-${msgId}`);
if (el) {
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
el.classList.add('bg-vortex-500/20');
setTimeout(() => el.classList.remove('bg-vortex-500/20'), 2000);
el.classList.add('bg-knot-500/20');
setTimeout(() => el.classList.remove('bg-knot-500/20'), 2000);
setProfileUserId(null);
}
}}
@@ -943,8 +943,8 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
const el = document.getElementById(`msg-${msgId}`);
if (el) {
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
el.classList.add('bg-vortex-500/20');
setTimeout(() => el.classList.remove('bg-vortex-500/20'), 2000);
el.classList.add('bg-knot-500/20');
setTimeout(() => el.classList.remove('bg-knot-500/20'), 2000);
setShowGroupSettings(false);
}
}}
+4 -4
View File
@@ -224,7 +224,7 @@ export default function DatePicker({ value, onChange }: DatePickerProps) {
isSelected(day)
? 'bg-accent text-white font-semibold shadow-lg shadow-accent/30'
: isToday(day)
? 'text-vortex-400 font-semibold ring-1 ring-vortex-500/50'
? 'text-knot-400 font-semibold ring-1 ring-knot-500/50'
: 'text-zinc-300 hover:bg-surface-hover'
}`}
>
@@ -261,7 +261,7 @@ export default function DatePicker({ value, onChange }: DatePickerProps) {
isSelectedMonth
? 'bg-accent text-white shadow-lg shadow-accent/30'
: isCurrentMonth
? 'text-vortex-400 ring-1 ring-vortex-500/50'
? 'text-knot-400 ring-1 ring-knot-500/50'
: 'text-zinc-300 hover:bg-surface-hover'
}`}
>
@@ -294,7 +294,7 @@ export default function DatePicker({ value, onChange }: DatePickerProps) {
isSelectedYear
? 'bg-accent text-white shadow-lg shadow-accent/30'
: isCurrentYear
? 'text-vortex-400 ring-1 ring-vortex-500/50'
? 'text-knot-400 ring-1 ring-knot-500/50'
: 'text-zinc-300 hover:bg-surface-hover'
}`}
>
@@ -323,7 +323,7 @@ export default function DatePicker({ value, onChange }: DatePickerProps) {
onChange(`${today.getFullYear()}-${m}-${d}`);
setOpen(false);
}}
className="text-xs text-vortex-400 hover:text-vortex-300 transition-colors"
className="text-xs text-knot-400 hover:text-knot-300 transition-colors"
>
{t('today')}
</button>
+1 -1
View File
@@ -182,7 +182,7 @@ export default function EmojiPicker({ onSelect, onSelectGif, onClose }: EmojiPic
<div className="flex-1 flex flex-col items-center justify-center p-6 text-center">
<p className="text-sm text-zinc-400 mb-2">Klipy API Key required</p>
<p className="text-xs text-zinc-500 mb-3">{t('openConsoleRun')}</p>
<code className="text-xs bg-black/30 px-3 py-1.5 rounded-lg text-vortex-400">
<code className="text-xs bg-black/30 px-3 py-1.5 rounded-lg text-knot-400">
VITE_KLIPY_API_KEY in .env
</code>
</div>
+1 -1
View File
@@ -68,7 +68,7 @@ export default function ForwardModal({ onClose, onForward }: ForwardModalProps)
placeholder={t('searchChats') || 'Поиск чатов'}
value={search}
onChange={(e) => setSearch(e.target.value)}
className="w-full bg-black/20 border border-white/10 rounded-xl py-2.5 pl-10 pr-4 text-white placeholder-zinc-500 focus:outline-none focus:border-vortex-500 transition-colors"
className="w-full bg-black/20 border border-white/10 rounded-xl py-2.5 pl-10 pr-4 text-white placeholder-zinc-500 focus:outline-none focus:border-knot-500 transition-colors"
/>
</div>
+3 -3
View File
@@ -836,7 +836,7 @@ export default function GroupCallModal({ isOpen, onClose, chatId, chatName, call
>
<div className="absolute inset-0 pointer-events-none opacity-40">
<div className="absolute top-[10%] left-[20%] w-[50vh] h-[50vh] bg-emerald-500/30 rounded-full blur-[120px] animate-float" />
<div className="absolute bottom-[10%] right-[20%] w-[50vh] h-[50vh] bg-vortex-500/20 rounded-full blur-[120px] animate-float-delayed" />
<div className="absolute bottom-[10%] right-[20%] w-[50vh] h-[50vh] bg-knot-500/20 rounded-full blur-[120px] animate-float-delayed" />
</div>
<motion.div
@@ -860,7 +860,7 @@ export default function GroupCallModal({ isOpen, onClose, chatId, chatName, call
step="0.05"
value={remoteVolume}
onChange={e => handleVolumeChange(parseFloat(e.target.value))}
className="w-full h-1.5 rounded-full appearance-none bg-zinc-600 accent-vortex-500 cursor-pointer"
className="w-full h-1.5 rounded-full appearance-none bg-zinc-600 accent-knot-500 cursor-pointer"
/>
<span className="text-xs text-zinc-300 w-8 text-right">{Math.round(remoteVolume * 100)}%</span>
</div>
@@ -878,7 +878,7 @@ export default function GroupCallModal({ isOpen, onClose, chatId, chatName, call
key={mic.deviceId}
onClick={() => switchMicrophone(mic.deviceId)}
className={`w-full text-left px-4 py-2.5 text-sm transition-colors ${activeMicId === mic.deviceId
? 'text-vortex-400 bg-vortex-500/20 font-medium'
? 'text-knot-400 bg-knot-500/20 font-medium'
: 'text-zinc-200 hover:bg-zinc-700'
}`}
>
+14 -14
View File
@@ -282,7 +282,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
{groups.map((g, i) => (
<div key={i}>
<div className="sticky top-0 z-10 bg-black/60 backdrop-blur-md px-3 py-1.5 mb-1.5 shadow-sm border-y border-white/5">
<span className="text-[10px] font-bold text-vortex-300 uppercase tracking-widest">{g.dateStr}</span>
<span className="text-[10px] font-bold text-knot-300 uppercase tracking-widest">{g.dateStr}</span>
</div>
<div className={gridClass || "flex flex-col gap-0.5"}>
{g.items.map(({item, idx}) => renderItem(item, idx))}
@@ -339,7 +339,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
{/* Avatar */}
<div className="flex-shrink-0 flex flex-col items-center py-6 px-6 overflow-y-auto max-h-[50%] custom-scrollbar">
<div className="relative group">
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-40 h-40 bg-vortex-500/20 rounded-full blur-[40px] pointer-events-none" />
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-40 h-40 bg-knot-500/20 rounded-full blur-[40px] pointer-events-none" />
<div className="relative z-10 p-1.5 rounded-full bg-gradient-to-br from-white/10 to-transparent backdrop-blur-md border border-white/10 shadow-2xl">
{chat.avatar ? (
<img
@@ -348,7 +348,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
className="w-32 h-32 rounded-full object-cover shadow-inner"
/>
) : (
<div className="w-32 h-32 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white font-bold text-4xl shadow-inner">
<div className="w-32 h-32 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white font-bold text-4xl shadow-inner">
{initials}
</div>
)}
@@ -420,7 +420,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
{chat.name || t('group')}
</h3>
{isAdmin && (
<Edit3 size={16} className="text-vortex-400 opacity-0 group-hover/name:opacity-100 transition-opacity" />
<Edit3 size={16} className="text-knot-400 opacity-0 group-hover/name:opacity-100 transition-opacity" />
)}
</div>
)}
@@ -468,7 +468,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
</p>
{isAdmin && (
<div className="absolute top-3 right-3 opacity-0 group-hover/desc:opacity-100 transition-opacity">
<Edit3 size={14} className="text-vortex-400" />
<Edit3 size={14} className="text-knot-400" />
</div>
)}
</div>
@@ -489,7 +489,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
onClick={() => setActiveTab(tab.key)}
className={`flex-1 flex flex-col items-center justify-center gap-1 py-2 text-[10px] font-bold uppercase tracking-widest transition-all ${
activeTab === tab.key
? 'bg-white/5 text-vortex-400'
? 'bg-white/5 text-knot-400'
: 'text-zinc-500 hover:text-zinc-300'
}`}
>
@@ -521,7 +521,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
setTimeout(() => searchInputRef.current?.focus(), 100);
}
}}
className="flex items-center gap-1 text-xs text-vortex-400 hover:text-vortex-300 transition-colors"
className="flex items-center gap-1 text-xs text-knot-400 hover:text-knot-300 transition-colors"
>
<UserPlus size={14} />
{t('addMember')}
@@ -563,7 +563,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
{u.avatar ? (
<img src={getMediaUrl(u.avatar)} alt="" className="w-8 h-8 rounded-full object-cover" />
) : (
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white text-xs font-bold">
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white text-xs font-bold">
{(u.displayName || u.username || '?')[0].toUpperCase()}
</div>
)}
@@ -571,7 +571,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
<p className="text-sm text-white truncate">{u.displayName || u.username}</p>
<p className="text-xs text-zinc-500">@{u.username}</p>
</div>
<UserPlus size={14} className="text-vortex-400 flex-shrink-0" />
<UserPlus size={14} className="text-knot-400 flex-shrink-0" />
</button>
))}
{searchQuery.trim() && !isSearching && searchResults.length === 0 && (
@@ -602,7 +602,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
{member.user.avatar ? (
<img src={getMediaUrl(member.user.avatar)} alt="" className="w-9 h-9 rounded-full object-cover" />
) : (
<div className="w-9 h-9 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white text-xs font-bold">
<div className="w-9 h-9 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white text-xs font-bold">
{(member.user.displayName || member.user.username || '?')[0].toUpperCase()}
</div>
)}
@@ -698,7 +698,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
download={m.filename || 'file'}
className="flex items-center gap-3 px-4 py-3 hover:bg-white/5 transition-colors"
>
<div className="w-8 h-8 rounded-lg bg-vortex-500/10 flex items-center justify-center flex-shrink-0 text-vortex-400">
<div className="w-8 h-8 rounded-lg bg-knot-500/10 flex items-center justify-center flex-shrink-0 text-knot-400">
<FileText size={16} />
</div>
@@ -734,7 +734,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
href={link}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 text-[13px] text-vortex-400 hover:underline truncate mb-1"
className="flex items-center gap-2 text-[13px] text-knot-400 hover:underline truncate mb-1"
>
<ExternalLink size={12} className="flex-shrink-0" />
{link}
@@ -743,7 +743,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
{msg.content && <p className="text-[11px] text-zinc-500 line-clamp-1">{msg.content}</p>}
<button
onClick={() => onGoToMessage?.(msg.id)}
className="absolute right-4 top-1/2 -translate-y-1/2 px-3 py-1.5 rounded-lg bg-black/40 hover:bg-vortex-500/20 text-zinc-300 hover:text-white text-[11px] font-medium opacity-0 group-hover:opacity-100 transition-all shadow-md z-10"
className="absolute right-4 top-1/2 -translate-y-1/2 px-3 py-1.5 rounded-lg bg-black/40 hover:bg-knot-500/20 text-zinc-300 hover:text-white text-[11px] font-medium opacity-0 group-hover:opacity-100 transition-all shadow-md z-10"
>
{t('showInChat')}
</button>
@@ -828,7 +828,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
max={3}
step={0.1}
onChange={(e) => setZoom(Number(e.target.value))}
className="flex-1 accent-vortex-500"
className="flex-1 accent-knot-500"
/>
</div>
+3 -3
View File
@@ -55,7 +55,7 @@ export default function LinkPreview({ url }: LinkPreviewProps) {
if (loading) {
return (
<div className="mt-2 text-xs text-vortex-400 opacity-70 italic border-l-[3px] border-vortex-500/50 pl-2">
<div className="mt-2 text-xs text-knot-400 opacity-70 italic border-l-[3px] border-knot-500/50 pl-2">
Загрузка предпросмотра...
</div>
);
@@ -78,11 +78,11 @@ export default function LinkPreview({ url }: LinkPreviewProps) {
href={url}
target="_blank"
rel="noopener noreferrer"
className="block mt-2 border-l-[3px] border-vortex-500 bg-black/20 rounded-r-lg overflow-hidden hover:bg-black/30 transition-colors"
className="block mt-2 border-l-[3px] border-knot-500 bg-black/20 rounded-r-lg overflow-hidden hover:bg-black/30 transition-colors"
onClick={(e) => e.stopPropagation()}
>
<div className="p-2.5 flex flex-col gap-1.5">
<div className="flex items-center gap-1.5 text-xs font-semibold text-vortex-400">
<div className="flex items-center gap-1.5 text-xs font-semibold text-knot-400">
{data.logo?.url && <img src={data.logo.url} alt="" className="w-3.5 h-3.5 rounded-sm object-cover" />}
<span className="truncate">{domain}</span>
</div>
+20 -20
View File
@@ -360,7 +360,7 @@ function MessageBubble({
<div
ref={bubbleRef}
className={`flex ${isMine ? 'justify-end' : 'justify-start'} group mb-0.5 relative transition-colors duration-200 ${selectionMode ? 'px-4 -mx-4 cursor-pointer hover:bg-white/5 rounded-xl' : ''
} ${isSelected ? 'bg-vortex-500/10 hover:bg-vortex-500/20' : ''}`}
} ${isSelected ? 'bg-knot-500/10 hover:bg-knot-500/20' : ''}`}
onClick={() => {
if (selectionMode) onToggleSelect?.(message.id);
}}
@@ -368,7 +368,7 @@ function MessageBubble({
>
{selectionMode && (
<div className="absolute left-1 top-1/2 -translate-y-1/2 w-5 h-5 rounded-full border border-white/30 flex items-center justify-center transition-colors">
{isSelected && <div className="w-5 h-5 rounded-full bg-vortex-500 flex items-center justify-center">
{isSelected && <div className="w-5 h-5 rounded-full bg-knot-500 flex items-center justify-center">
<Check size={12} className="text-white" />
</div>}
</div>
@@ -381,7 +381,7 @@ function MessageBubble({
{senderAvatar ? (
<img src={senderAvatar} alt="" className="w-8 h-8 rounded-full object-cover" />
) : (
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white text-xs font-semibold">
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white text-xs font-semibold">
{senderName[0]?.toUpperCase() || '?'}
</div>
)}
@@ -393,7 +393,7 @@ function MessageBubble({
<div className={`max-w-[65%] ${isMine ? 'items-end' : 'items-start'} flex flex-col`}>
{!isMine && showAvatar && (
<button
className="text-xs font-medium text-vortex-400 ml-3 mb-0.5 hover:underline"
className="text-xs font-medium text-knot-400 ml-3 mb-0.5 hover:underline"
onClick={() => onViewProfile?.(message.senderId)}
>
{senderName}
@@ -403,7 +403,7 @@ function MessageBubble({
{/* Reply */}
{message.replyTo && (
<div
className="mx-3 mb-1 px-3 py-1.5 rounded-lg border-l-2 border-vortex-500 bg-vortex-500/10 max-w-full cursor-pointer hover:bg-vortex-500/20 transition-colors"
className="mx-3 mb-1 px-3 py-1.5 rounded-lg border-l-2 border-knot-500 bg-knot-500/10 max-w-full cursor-pointer hover:bg-knot-500/20 transition-colors"
onClick={() => {
const el = document.getElementById(`msg-${message.replyToId}`);
if (el) {
@@ -413,7 +413,7 @@ function MessageBubble({
}
}}
>
<p className="text-xs font-medium text-vortex-400 truncate">
<p className="text-xs font-medium text-knot-400 truncate">
{message.replyTo.sender?.displayName || message.replyTo.sender?.username}
</p>
<div className="flex items-center gap-1">
@@ -458,7 +458,7 @@ function MessageBubble({
) : message.storyMediaType === 'image' ? (
<img src={getMediaUrl(message.storyMediaUrl)} className="w-full h-full object-cover" alt="" />
) : (
<div className="w-full h-full flex items-center justify-center bg-vortex-500/20"><FileText size={10} className="text-vortex-400" /></div>
<div className="w-full h-full flex items-center justify-center bg-knot-500/20"><FileText size={10} className="text-knot-400" /></div>
)}
</div>
)}
@@ -555,13 +555,13 @@ function MessageBubble({
/>
<button
onClick={toggleAudio}
className={`w-9 h-9 rounded-full flex items-center justify-center flex-shrink-0 ${isMine ? 'bg-white/20 hover:bg-white/30' : 'bg-vortex-500/20 hover:bg-vortex-500/30'
className={`w-9 h-9 rounded-full flex items-center justify-center flex-shrink-0 ${isMine ? 'bg-white/20 hover:bg-white/30' : 'bg-knot-500/20 hover:bg-knot-500/30'
} transition-colors`}
>
{isPlaying ? (
<Pause size={16} className={isMine ? 'text-white' : 'text-vortex-400'} />
<Pause size={16} className={isMine ? 'text-white' : 'text-knot-400'} />
) : (
<Play size={16} className={`${isMine ? 'text-white' : 'text-vortex-400'} ml-0.5`} />
<Play size={16} className={`${isMine ? 'text-white' : 'text-knot-400'} ml-0.5`} />
)}
</button>
<div className="flex-1 min-w-0">
@@ -586,7 +586,7 @@ function MessageBubble({
<div
key={i}
className={`flex-1 rounded-full transition-colors duration-150 ${isActive
? isMine ? 'bg-white/80' : 'bg-vortex-400'
? isMine ? 'bg-white/80' : 'bg-knot-400'
: isMine ? 'bg-white/20' : 'bg-white/10'
}`}
style={{ height: `${barHeight}%` }}
@@ -610,7 +610,7 @@ function MessageBubble({
<div className="min-w-[220px]">
{audioMedia?.filename && (
<div className="flex items-center gap-2 mb-2">
<Volume2 size={14} className={isMine ? 'text-white/60' : 'text-vortex-400'} />
<Volume2 size={14} className={isMine ? 'text-white/60' : 'text-knot-400'} />
<span className={`text-xs truncate ${isMine ? 'text-white/70' : 'text-zinc-400'}`}>{audioMedia.filename}</span>
</div>
)}
@@ -623,13 +623,13 @@ function MessageBubble({
/>
<button
onClick={toggleAudio}
className={`w-9 h-9 rounded-full flex items-center justify-center flex-shrink-0 ${isMine ? 'bg-white/20 hover:bg-white/30' : 'bg-vortex-500/20 hover:bg-vortex-500/30'
className={`w-9 h-9 rounded-full flex items-center justify-center flex-shrink-0 ${isMine ? 'bg-white/20 hover:bg-white/30' : 'bg-knot-500/20 hover:bg-knot-500/30'
} transition-colors`}
>
{isPlaying ? (
<Pause size={16} className={isMine ? 'text-white' : 'text-vortex-400'} />
<Pause size={16} className={isMine ? 'text-white' : 'text-knot-400'} />
) : (
<Play size={16} className={`${isMine ? 'text-white' : 'text-vortex-400'} ml-0.5`} />
<Play size={16} className={`${isMine ? 'text-white' : 'text-knot-400'} ml-0.5`} />
)}
</button>
<div className="flex-1 min-w-0">
@@ -643,7 +643,7 @@ function MessageBubble({
<div
key={i}
className={`flex-1 rounded-full transition-colors duration-150 ${isActive
? isMine ? 'bg-white/80' : 'bg-vortex-400'
? isMine ? 'bg-white/80' : 'bg-knot-400'
: isMine ? 'bg-white/20' : 'bg-white/10'
}`}
style={{ height: `${barHeight}%` }}
@@ -676,9 +676,9 @@ function MessageBubble({
className={`flex items-center gap-3 p-2 rounded-xl ${isMine ? 'bg-white/10 hover:bg-white/15' : 'bg-surface-tertiary hover:bg-surface-hover'
} transition-colors mb-1`}
>
<div className={`w-10 h-10 rounded-lg flex items-center justify-center ${isMine ? 'bg-white/20' : 'bg-vortex-500/20'
<div className={`w-10 h-10 rounded-lg flex items-center justify-center ${isMine ? 'bg-white/20' : 'bg-knot-500/20'
}`}>
<FileText size={20} className={isMine ? 'text-white' : 'text-vortex-400'} />
<FileText size={20} className={isMine ? 'text-white' : 'text-knot-400'} />
</div>
<div className="flex-1 min-w-0">
<p className="text-sm truncate">{m.filename || t('fileLabel')}</p>
@@ -743,7 +743,7 @@ function MessageBubble({
key={emoji}
onClick={() => handleReaction(emoji)}
className={`flex items-center gap-1 px-2 py-0.5 rounded-full text-xs transition-colors ${data.isMine
? 'bg-vortex-500/30 border border-vortex-500/50'
? 'bg-knot-500/30 border border-knot-500/50'
: 'bg-surface-tertiary border border-border hover:border-zinc-600'
}`}
title={data.users.join(', ')}
@@ -763,7 +763,7 @@ function MessageBubble({
{senderAvatar ? (
<img src={senderAvatar} alt="" className="w-8 h-8 rounded-full object-cover" />
) : (
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white text-xs font-semibold">
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white text-xs font-semibold">
{senderName[0]?.toUpperCase() || '?'}
</div>
)}
+18 -18
View File
@@ -574,9 +574,9 @@ export default function MessageInput({ chatId }: MessageInputProps) {
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="absolute inset-0 z-50 rounded-[2rem] mx-6 mb-6 mt-2 bg-vortex-500/10 border-2 border-dashed border-vortex-400 backdrop-blur-sm flex items-center justify-center pointer-events-none"
className="absolute inset-0 z-50 rounded-[2rem] mx-6 mb-6 mt-2 bg-knot-500/10 border-2 border-dashed border-knot-400 backdrop-blur-sm flex items-center justify-center pointer-events-none"
>
<div className="flex flex-col items-center gap-2 text-vortex-300">
<div className="flex flex-col items-center gap-2 text-knot-300">
<FileText size={32} className="animate-bounce" />
<p className="font-semibold">{t('dropFileHere')}</p>
</div>
@@ -594,16 +594,16 @@ export default function MessageInput({ chatId }: MessageInputProps) {
className="mb-2 max-w-3xl mx-auto overflow-hidden px-1.5"
>
<div className="flex items-center gap-3 px-4 py-2.5 bg-white/[0.04] backdrop-blur-2xl border border-white/10 rounded-2xl relative shadow-xl">
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-3/5 bg-gradient-to-b from-vortex-400 to-purple-500 rounded-r-md" />
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-3/5 bg-gradient-to-b from-knot-400 to-purple-500 rounded-r-md" />
<div className="w-6 h-6 rounded-full bg-white/5 flex items-center justify-center flex-shrink-0">
{editingMessage ? (
<Pencil size={12} className="text-vortex-400" />
<Pencil size={12} className="text-knot-400" />
) : (
<Reply size={12} className="text-vortex-400" />
<Reply size={12} className="text-knot-400" />
)}
</div>
<div className="flex-1 min-w-0 flex flex-col justify-center">
<p className="text-xs font-semibold text-vortex-400 mb-0.5">
<p className="text-xs font-semibold text-knot-400 mb-0.5">
{editingMessage
? t('editing')
: `${t('replyTo')} ${replyTo?.sender?.displayName || replyTo?.sender?.username || ''}`}
@@ -652,8 +652,8 @@ export default function MessageInput({ chatId }: MessageInputProps) {
className="w-full h-full object-cover"
/>
) : att.type === 'video' ? (
<div className="w-full h-full bg-vortex-500/20 flex items-center justify-center">
<ImageIcon size={20} className="text-vortex-400" />
<div className="w-full h-full bg-knot-500/20 flex items-center justify-center">
<ImageIcon size={20} className="text-knot-400" />
</div>
) : att.type === 'audio' ? (
<div className="w-full h-full bg-emerald-500/20 flex items-center justify-center">
@@ -710,7 +710,7 @@ export default function MessageInput({ chatId }: MessageInputProps) {
{liveBars.map((h, i) => (
<div
key={i}
className="flex-1 bg-vortex-400/60 rounded-full transition-all duration-100"
className="flex-1 bg-knot-400/60 rounded-full transition-all duration-100"
style={{ height: `${h}%` }}
/>
))}
@@ -724,7 +724,7 @@ export default function MessageInput({ chatId }: MessageInputProps) {
</button>
</div>
) : (
<div className="flex items-end gap-1.5 bg-white/[0.04] backdrop-blur-[40px] rounded-[2rem] border border-white/[0.08] p-2 w-full max-w-3xl mx-auto transition-all duration-300 hover:bg-white/[0.06] focus-within:bg-white/[0.08] shadow-[0_8px_32px_rgba(0,0,0,0.3)] focus-within:shadow-[0_8px_40px_rgba(99,102,241,0.15)] focus-within:border-vortex-500/30 group">
<div className="flex items-end gap-1.5 bg-white/[0.04] backdrop-blur-[40px] rounded-[2rem] border border-white/[0.08] p-2 w-full max-w-3xl mx-auto transition-all duration-300 hover:bg-white/[0.06] focus-within:bg-white/[0.08] shadow-[0_8px_32px_rgba(0,0,0,0.3)] focus-within:shadow-[0_8px_40px_rgba(99,102,241,0.15)] focus-within:border-knot-500/30 group">
{/* Attach */}
<div className="relative mb-0.5 ml-1 flex-shrink-0 self-center">
<button
@@ -747,8 +747,8 @@ export default function MessageInput({ chatId }: MessageInputProps) {
onClick={() => imageInputRef.current?.click()}
className="flex items-center gap-4 w-full px-3 py-3 rounded-xl text-sm font-medium text-zinc-200 hover:bg-white/5 hover:text-white transition-all group"
>
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-vortex-400/20 to-purple-500/20 flex items-center justify-center ring-1 ring-vortex-400/30 group-hover:scale-110 transition-transform shadow-inner">
<ImageIcon size={18} className="text-vortex-400" />
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-knot-400/20 to-purple-500/20 flex items-center justify-center ring-1 ring-knot-400/30 group-hover:scale-110 transition-transform shadow-inner">
<ImageIcon size={18} className="text-knot-400" />
</div>
{t('photoVideo')}
</button>
@@ -805,7 +805,7 @@ export default function MessageInput({ chatId }: MessageInputProps) {
{m.user.avatar ? (
<img src={m.user.avatar} className="w-7 h-7 rounded-full object-cover" alt="" />
) : (
<div className="w-7 h-7 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white text-xs font-semibold">
<div className="w-7 h-7 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white text-xs font-semibold">
{(m.user.displayName || m.user.username)[0]?.toUpperCase()}
</div>
)}
@@ -923,7 +923,7 @@ export default function MessageInput({ chatId }: MessageInputProps) {
<ChevronLeft size={16} />
</button>
)}
<Clock size={16} className="text-vortex-400" />
<Clock size={16} className="text-knot-400" />
<span className="text-sm font-medium text-zinc-200">{t('scheduleMessage')}</span>
</div>
@@ -984,7 +984,7 @@ export default function MessageInput({ chatId }: MessageInputProps) {
setScheduleMinute(String(now.getMinutes()).padStart(2, '0'));
setScheduleStep('custom');
}}
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm text-vortex-400 hover:bg-white/10 transition-colors text-left"
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm text-knot-400 hover:bg-white/10 transition-colors text-left"
>
<Calendar size={15} className="flex-shrink-0" />
{t('scheduleCustom')}
@@ -1197,7 +1197,7 @@ function ScheduleCalendar({
: isPast(day)
? 'text-zinc-600 cursor-not-allowed'
: isToday(day)
? 'text-vortex-400 font-semibold ring-1 ring-vortex-500/50'
? 'text-knot-400 font-semibold ring-1 ring-knot-500/50'
: 'text-zinc-300 hover:bg-white/10'
}`}
>
@@ -1217,7 +1217,7 @@ function ScheduleCalendar({
<select
value={hour}
onChange={(e) => setHour(e.target.value)}
className="flex-1 bg-white/5 border border-white/10 rounded-lg px-2 py-1.5 text-sm text-zinc-200 focus:outline-none focus:border-vortex-500/50 appearance-none text-center"
className="flex-1 bg-white/5 border border-white/10 rounded-lg px-2 py-1.5 text-sm text-zinc-200 focus:outline-none focus:border-knot-500/50 appearance-none text-center"
>
{Array.from({ length: 24 }, (_, i) => String(i).padStart(2, '0')).map((h) => (
<option key={h} value={h} className="bg-zinc-800">{h}</option>
@@ -1227,7 +1227,7 @@ function ScheduleCalendar({
<select
value={minute}
onChange={(e) => setMinute(e.target.value)}
className="flex-1 bg-white/5 border border-white/10 rounded-lg px-2 py-1.5 text-sm text-zinc-200 focus:outline-none focus:border-vortex-500/50 appearance-none text-center"
className="flex-1 bg-white/5 border border-white/10 rounded-lg px-2 py-1.5 text-sm text-zinc-200 focus:outline-none focus:border-knot-500/50 appearance-none text-center"
>
{Array.from({ length: 60 }, (_, i) => String(i).padStart(2, '0')).map((m) => (
<option key={m} value={m} className="bg-zinc-800">{m}</option>
+11 -11
View File
@@ -163,12 +163,12 @@ export default function NewChatModal({ onClose }: NewChatModalProps) {
{selectedUsers.map((u) => (
<div
key={u.id}
className="flex items-center gap-2 px-3 py-1.5 rounded-full bg-vortex-500/20 border border-vortex-500/30"
className="flex items-center gap-2 px-3 py-1.5 rounded-full bg-knot-500/20 border border-knot-500/30"
>
{u.avatar ? (
<img src={u.avatar} alt="" className="w-5 h-5 rounded-full object-cover" />
) : (
<div className="w-5 h-5 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white text-[9px] font-semibold">
<div className="w-5 h-5 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white text-[9px] font-semibold">
{(u.displayName || u.username)?.[0]?.toUpperCase()}
</div>
)}
@@ -207,7 +207,7 @@ export default function NewChatModal({ onClose }: NewChatModalProps) {
onClick={() => setMode('group-select')}
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl bg-surface-tertiary hover:bg-surface-hover transition-colors border border-border"
>
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center">
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center">
<Users size={18} className="text-white" />
</div>
<div className="text-left">
@@ -224,7 +224,7 @@ export default function NewChatModal({ onClose }: NewChatModalProps) {
<button
key={u.id}
onClick={() => setSelectedUsers((prev) => prev.filter((p) => p.id !== u.id))}
className="flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-vortex-500/20 border border-vortex-500/30 text-xs text-white hover:bg-vortex-500/30 transition-colors"
className="flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-knot-500/20 border border-knot-500/30 text-xs text-white hover:bg-knot-500/30 transition-colors"
>
{(u.displayName || u.username)}
<X size={11} />
@@ -254,7 +254,7 @@ export default function NewChatModal({ onClose }: NewChatModalProps) {
<div className="max-h-72 overflow-y-auto px-2 pb-4">
{isLoading ? (
<div className="flex justify-center py-8">
<div className="w-5 h-5 border-2 border-vortex-500 border-t-transparent rounded-full animate-spin" />
<div className="w-5 h-5 border-2 border-knot-500 border-t-transparent rounded-full animate-spin" />
</div>
) : query.trim().length >= 3 && users.length > 0 ? (
users.map((u) => (
@@ -263,7 +263,7 @@ export default function NewChatModal({ onClose }: NewChatModalProps) {
onClick={() => handleSelectUser(u)}
className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-xl transition-colors ${
isSelected(u.id)
? 'bg-vortex-500/15 border border-vortex-500/30'
? 'bg-knot-500/15 border border-knot-500/30'
: 'hover:bg-surface-hover border border-transparent'
}`}
>
@@ -271,7 +271,7 @@ export default function NewChatModal({ onClose }: NewChatModalProps) {
{u.avatar ? (
<img src={u.avatar} alt="" className="w-10 h-10 rounded-full object-cover" />
) : (
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white font-semibold text-sm">
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white font-semibold text-sm">
{(u.displayName || u.username)?.[0]?.toUpperCase() || '?'}
</div>
)}
@@ -288,7 +288,7 @@ export default function NewChatModal({ onClose }: NewChatModalProps) {
{mode === 'group-select' && (
<div className={`w-5 h-5 rounded-full border-2 flex items-center justify-center flex-shrink-0 transition-colors ${
isSelected(u.id)
? 'bg-vortex-500 border-vortex-500'
? 'bg-knot-500 border-knot-500'
: 'border-zinc-600'
}`}>
{isSelected(u.id) && <Check size={12} className="text-white" />}
@@ -313,7 +313,7 @@ export default function NewChatModal({ onClose }: NewChatModalProps) {
onClick={() => handleSelectUser(u)}
className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-xl transition-colors ${
isSelected(u.id)
? 'bg-vortex-500/15 border border-vortex-500/30'
? 'bg-knot-500/15 border border-knot-500/30'
: 'hover:bg-surface-hover border border-transparent'
}`}
>
@@ -321,7 +321,7 @@ export default function NewChatModal({ onClose }: NewChatModalProps) {
{u.avatar ? (
<img src={u.avatar} alt="" className="w-10 h-10 rounded-full object-cover" />
) : (
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white font-semibold text-sm">
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white font-semibold text-sm">
{(u.displayName || u.username)?.[0]?.toUpperCase() || '?'}
</div>
)}
@@ -338,7 +338,7 @@ export default function NewChatModal({ onClose }: NewChatModalProps) {
{mode === 'group-select' && (
<div className={`w-5 h-5 rounded-full border-2 flex items-center justify-center flex-shrink-0 transition-colors ${
isSelected(u.id)
? 'bg-vortex-500 border-vortex-500'
? 'bg-knot-500 border-knot-500'
: 'border-zinc-600'
}`}>
{isSelected(u.id) && <Check size={12} className="text-white" />}
+20 -20
View File
@@ -247,7 +247,7 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
{ icon: Settings, label: t('settings'), onClick: () => changeView('settings') },
{ divider: true },
{ icon: Info, label: t('aboutApp'), subtitle: 'SelfHost Messenger v1.0', onClick: () => changeView('about') },
{ icon: Info, label: t('aboutApp'), subtitle: 'Knot Messenger v1.0', onClick: () => changeView('about') },
];
// Slide direction for animations
@@ -264,8 +264,8 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
{/* ── Premium header with avatar ── */}
<div className="relative overflow-hidden flex-shrink-0">
{/* Animated gradient backdrop */}
<div className="absolute inset-0 bg-gradient-to-br from-vortex-500/40 via-purple-600/25 to-transparent pointer-events-none" />
<div className="absolute -top-20 -right-20 w-56 h-56 bg-vortex-500/15 rounded-full blur-[80px] pointer-events-none" />
<div className="absolute inset-0 bg-gradient-to-br from-knot-500/40 via-purple-600/25 to-transparent pointer-events-none" />
<div className="absolute -top-20 -right-20 w-56 h-56 bg-knot-500/15 rounded-full blur-[80px] pointer-events-none" />
<div className="absolute -bottom-10 -left-10 w-40 h-40 bg-purple-600/10 rounded-full blur-[60px] pointer-events-none" />
<div className="relative p-6 pb-5">
@@ -295,8 +295,8 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
{user?.displayName || user?.username}
</h3>
<div className="flex items-center gap-1.5 mt-1.5">
<AtSign size={12} className="text-vortex-400" />
<span className="text-sm font-medium text-vortex-100/70">{user?.username}</span>
<AtSign size={12} className="text-knot-400" />
<span className="text-sm font-medium text-knot-100/70">{user?.username}</span>
</div>
</div>
{/* Bottom fade line */}
@@ -314,15 +314,15 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
onClick={item.onClick}
className="group w-full flex items-center gap-3.5 px-4 py-3 rounded-2xl text-left transition-all duration-200 hover:bg-white/[0.06] active:scale-[0.98]"
>
<div className="w-9 h-9 rounded-xl bg-white/[0.06] group-hover:bg-vortex-500/15 flex items-center justify-center transition-all duration-200 border border-white/[0.04] group-hover:border-vortex-500/20">
<Icon size={17} className="text-zinc-400 group-hover:text-vortex-400 transition-colors duration-200" />
<div className="w-9 h-9 rounded-xl bg-white/[0.06] group-hover:bg-knot-500/15 flex items-center justify-center transition-all duration-200 border border-white/[0.04] group-hover:border-knot-500/20">
<Icon size={17} className="text-zinc-400 group-hover:text-knot-400 transition-colors duration-200" />
</div>
<div className="min-w-0 flex-1">
<p className="text-[13.5px] font-medium text-zinc-200 group-hover:text-white transition-colors">{item.label}</p>
{item.subtitle && <p className="text-[11px] text-zinc-500 mt-0.5">{item.subtitle}</p>}
</div>
{'badge' in item && item.badge ? (
<span className="bg-gradient-to-r from-vortex-500 to-purple-600 text-white text-[11px] font-bold min-w-[22px] h-[22px] px-1.5 rounded-full flex items-center justify-center flex-shrink-0 shadow-[0_0_12px_rgba(168,85,247,0.4)]">
<span className="bg-gradient-to-r from-knot-500 to-purple-600 text-white text-[11px] font-bold min-w-[22px] h-[22px] px-1.5 rounded-full flex items-center justify-center flex-shrink-0 shadow-[0_0_12px_rgba(168,85,247,0.4)]">
{item.badge}
</span>
) : (
@@ -380,23 +380,23 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
<div className="space-y-1">
<button
onClick={() => setLang('ru')}
className={`w-full flex items-center gap-4 px-3 py-3 rounded-xl transition-colors ${lang === 'ru' ? 'bg-vortex-500/15 ring-1 ring-vortex-500/30' : 'bg-surface-tertiary/50 hover:bg-surface-hover'}`}
className={`w-full flex items-center gap-4 px-3 py-3 rounded-xl transition-colors ${lang === 'ru' ? 'bg-knot-500/15 ring-1 ring-knot-500/30' : 'bg-surface-tertiary/50 hover:bg-surface-hover'}`}
>
<span className="text-lg">🇷🇺</span>
<div className="flex-1 min-w-0 text-left">
<p className="text-sm text-zinc-200">Русский</p>
</div>
{lang === 'ru' && <Check size={16} className="text-vortex-400" />}
{lang === 'ru' && <Check size={16} className="text-knot-400" />}
</button>
<button
onClick={() => setLang('en')}
className={`w-full flex items-center gap-4 px-3 py-3 rounded-xl transition-colors ${lang === 'en' ? 'bg-vortex-500/15 ring-1 ring-vortex-500/30' : 'bg-surface-tertiary/50 hover:bg-surface-hover'}`}
className={`w-full flex items-center gap-4 px-3 py-3 rounded-xl transition-colors ${lang === 'en' ? 'bg-knot-500/15 ring-1 ring-knot-500/30' : 'bg-surface-tertiary/50 hover:bg-surface-hover'}`}
>
<span className="text-lg">🇬🇧</span>
<div className="flex-1 min-w-0 text-left">
<p className="text-sm text-zinc-200">English</p>
</div>
{lang === 'en' && <Check size={16} className="text-vortex-400" />}
{lang === 'en' && <Check size={16} className="text-knot-400" />}
</button>
</div>
</div>
@@ -419,7 +419,7 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
<p className="text-sm text-zinc-200">{t('hideStoryViews')}</p>
<p className="text-[11px] text-zinc-500 mt-0.5">{t('hideStoryViewsDesc')}</p>
</div>
<div className={`w-10 h-6 rounded-full transition-colors flex items-center px-0.5 ${user?.hideStoryViews ? 'bg-vortex-500' : 'bg-zinc-600'}`}>
<div className={`w-10 h-6 rounded-full transition-colors flex items-center px-0.5 ${user?.hideStoryViews ? 'bg-knot-500' : 'bg-zinc-600'}`}>
<div className={`w-5 h-5 rounded-full bg-white shadow transition-transform ${user?.hideStoryViews ? 'translate-x-4' : 'translate-x-0'}`} />
</div>
</button>
@@ -430,7 +430,7 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
<div className="flex items-center gap-4 px-3 py-3 rounded-xl bg-surface-tertiary/50">
<Info size={18} className="text-zinc-400" />
<div className="flex-1 min-w-0">
<p className="text-sm text-zinc-200">SelfHost Messenger</p>
<p className="text-sm text-zinc-200">Knot Messenger</p>
<p className="text-xs text-zinc-500">{t('version')} 1.0.0</p>
</div>
</div>
@@ -603,7 +603,7 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
{u.avatar ? (
<img src={u.avatar} alt="" className="w-10 h-10 rounded-full object-cover" />
) : (
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white font-bold text-sm">
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white font-bold text-sm">
{(u.displayName || u.username || '?')[0].toUpperCase()}
</div>
)}
@@ -613,7 +613,7 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
</div>
<button
onClick={() => handleSendFriendRequest(u.id)}
className="p-2 rounded-lg bg-vortex-500/20 text-vortex-400 hover:bg-vortex-500/30 transition-colors"
className="p-2 rounded-lg bg-knot-500/20 text-knot-400 hover:bg-knot-500/30 transition-colors"
title={t('addFriend')}
>
<UserPlus size={16} />
@@ -638,7 +638,7 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
{req.user.avatar ? (
<img src={req.user.avatar} alt="" className="w-10 h-10 rounded-full object-cover" />
) : (
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white font-bold text-sm">
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white font-bold text-sm">
{(req.user.displayName || req.user.username || '?')[0].toUpperCase()}
</div>
)}
@@ -683,7 +683,7 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
{friend.avatar ? (
<img src={friend.avatar} alt="" className="w-10 h-10 rounded-full object-cover" />
) : (
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white font-bold text-sm">
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white font-bold text-sm">
{(friend.displayName || friend.username || '?')[0].toUpperCase()}
</div>
)}
@@ -729,12 +729,12 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
<div className="absolute inset-0 rounded-2xl bg-gradient-to-br from-white/[0.05] to-transparent pointer-events-none" />
<MessageSquare size={36} className="text-accent drop-shadow-md relative z-10" />
</div>
<h2 className="text-xl font-bold gradient-text mb-1">SelfHost Messenger</h2>
<h2 className="text-xl font-bold gradient-text mb-1">Knot Messenger</h2>
<p className="text-sm text-zinc-400 mb-6">{t('version')} 1.0.0</p>
<div className="text-xs text-zinc-500 space-y-1">
<p>{t('modernMessenger')}</p>
<p>{t('onPrivacy')}</p>
<p className="mt-4 text-zinc-600">© 2026 SelfHost Team</p>
<p className="mt-4 text-zinc-600">© 2026 Knot Team</p>
</div>
</div>
</motion.div>
+1 -1
View File
@@ -113,7 +113,7 @@ export default function Sidebar() {
<div className="absolute inset-0 rounded-xl bg-gradient-to-br from-white/[0.05] to-transparent pointer-events-none" />
<MessageSquare size={18} className="text-accent drop-shadow-md relative z-10" />
</div>
<h1 className="text-lg font-bold gradient-text truncate">SelfHost Messenger</h1>
<h1 className="text-lg font-bold gradient-text truncate">Knot Messenger</h1>
</div>
<button
onClick={() => setShowNewChat(true)}
+3 -3
View File
@@ -703,13 +703,13 @@ export function CreateStoryModal({ onClose, onCreated }: CreateStoryModalProps)
<div className="flex border-b border-white/10">
<button
onClick={() => setMode('text')}
className={`flex-1 py-2.5 text-sm font-medium transition-colors ${mode === 'text' ? 'text-vortex-400 border-b-2 border-vortex-400' : 'text-zinc-400'}`}
className={`flex-1 py-2.5 text-sm font-medium transition-colors ${mode === 'text' ? 'text-knot-400 border-b-2 border-knot-400' : 'text-zinc-400'}`}
>
{t('textStory')}
</button>
<button
onClick={() => setMode('image')}
className={`flex-1 py-2.5 text-sm font-medium transition-colors ${mode === 'image' ? 'text-vortex-400 border-b-2 border-vortex-400' : 'text-zinc-400'}`}
className={`flex-1 py-2.5 text-sm font-medium transition-colors ${mode === 'image' ? 'text-knot-400 border-b-2 border-knot-400' : 'text-zinc-400'}`}
>
{t('mediaStory')}
</button>
@@ -740,7 +740,7 @@ export function CreateStoryModal({ onClose, onCreated }: CreateStoryModalProps)
onChange={e => setText(e.target.value)}
placeholder={t('typeYourStory')}
maxLength={200}
className="w-full bg-white/5 border border-white/10 rounded-xl px-3 py-2 text-sm text-zinc-200 resize-none h-20 mb-3 focus:outline-none focus:border-vortex-500/50"
className="w-full bg-white/5 border border-white/10 rounded-xl px-3 py-2 text-sm text-zinc-200 resize-none h-20 mb-3 focus:outline-none focus:border-knot-500/50"
/>
<div className="flex flex-wrap gap-2 mb-3">
+2 -2
View File
@@ -5,12 +5,12 @@ export default function TypingIndicator() {
const { t } = useLang();
return (
<div className="flex items-center gap-1 py-1">
<span className="text-xs text-vortex-400 font-medium">{t('typingText')}</span>
<span className="text-xs text-knot-400 font-medium">{t('typingText')}</span>
<div className="flex gap-0.5">
{[0, 1, 2].map((i) => (
<motion.span
key={i}
className="w-1 h-1 rounded-full bg-vortex-400"
className="w-1 h-1 rounded-full bg-knot-400"
animate={{ opacity: [0.3, 1, 0.3] }}
transition={{
duration: 1,
+22 -22
View File
@@ -144,7 +144,7 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
{groups.map((g, i) => (
<div key={i}>
<div className="sticky top-0 z-10 bg-black/60 backdrop-blur-md px-3 py-1.5 mb-1.5 shadow-sm border-y border-white/5">
<span className="text-[10px] font-bold text-vortex-300 uppercase tracking-widest">{g.dateStr}</span>
<span className="text-[10px] font-bold text-knot-300 uppercase tracking-widest">{g.dateStr}</span>
</div>
<div className={gridClass || "flex flex-col gap-0.5"}>
{g.items.map(({item, idx}) => renderItem(item, idx))}
@@ -375,7 +375,7 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
>
{/* Шапка */}
<div className="flex items-center justify-between p-5 border-b border-white/5 bg-white/5 relative overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-r from-vortex-500/20 to-purple-500/10 pointer-events-none" />
<div className="absolute inset-0 bg-gradient-to-r from-knot-500/20 to-purple-500/10 pointer-events-none" />
<h2 className="text-xl font-bold tracking-tight text-white drop-shadow-sm relative z-10 flex-1">
{(isSelf ? t('myProfile') : t('profileTitle')) as string}
</h2>
@@ -392,7 +392,7 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
<button
onClick={handleSave}
disabled={isSaving}
className="w-8 h-8 flex items-center justify-center rounded-full bg-vortex-500/20 text-vortex-400 hover:text-vortex-300 hover:bg-vortex-500/30 transition-all border border-vortex-500/30"
className="w-8 h-8 flex items-center justify-center rounded-full bg-knot-500/20 text-knot-400 hover:text-knot-300 hover:bg-knot-500/30 transition-all border border-knot-500/30"
>
{isSaving ? <Loader2 size={16} className="animate-spin" /> : <Check size={16} />}
</button>
@@ -409,7 +409,7 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
{isLoading ? (
<div className="flex-1 flex items-center justify-center">
<div className="w-8 h-8 border-2 border-vortex-500 border-t-transparent rounded-full animate-spin" />
<div className="w-8 h-8 border-2 border-knot-500 border-t-transparent rounded-full animate-spin" />
</div>
) : profile ? (
<div className="flex-1 flex flex-col overflow-hidden">
@@ -417,7 +417,7 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
<div className="flex-shrink-0 overflow-y-auto max-h-[50%] custom-scrollbar">
{/* Аватар */}
<div className="flex flex-col items-center pt-8 pb-4 px-6 relative overflow-visible">
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[240px] h-[240px] bg-vortex-500/10 rounded-full blur-[80px] pointer-events-none" />
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[240px] h-[240px] bg-knot-500/10 rounded-full blur-[80px] pointer-events-none" />
<div className="relative group">
{/* Spinning gradient glow ring */}
@@ -467,13 +467,13 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
{/* Имя */}
{isEditing ? (
<div className="mt-5 w-full max-w-[260px] relative">
<div className="absolute -inset-0.5 bg-gradient-to-r from-vortex-500 to-purple-500 rounded-2xl opacity-50 blur-sm pointer-events-none" />
<div className="absolute -inset-0.5 bg-gradient-to-r from-knot-500 to-purple-500 rounded-2xl opacity-50 blur-sm pointer-events-none" />
<input
type="text"
value={displayName}
onChange={(e) => setDisplayName(e.target.value)}
placeholder={t('enterName')}
className="relative text-lg font-bold text-center text-white bg-black/40 border border-white/20 outline-none px-4 py-2.5 w-full rounded-2xl transition-colors focus:bg-black/60 focus:border-vortex-400 placeholder-white/30 truncate"
className="relative text-lg font-bold text-center text-white bg-black/40 border border-white/20 outline-none px-4 py-2.5 w-full rounded-2xl transition-colors focus:bg-black/60 focus:border-knot-400 placeholder-white/30 truncate"
/>
</div>
) : (
@@ -483,9 +483,9 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
)}
{/* Username (неизменяемый) */}
<div className="flex items-center gap-1.5 mt-2.5 bg-vortex-500/10 hover:bg-vortex-500/20 transition-colors px-4 py-1.5 rounded-full border border-vortex-500/20 backdrop-blur-sm cursor-default">
<AtSign size={14} className="text-vortex-400" />
<span className="text-sm font-semibold text-vortex-100">{profile.username}</span>
<div className="flex items-center gap-1.5 mt-2.5 bg-knot-500/10 hover:bg-knot-500/20 transition-colors px-4 py-1.5 rounded-full border border-knot-500/20 backdrop-blur-sm cursor-default">
<AtSign size={14} className="text-knot-400" />
<span className="text-sm font-semibold text-knot-100">{profile.username}</span>
</div>
{/* Онлайн статус */}
@@ -510,7 +510,7 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
<button
onClick={handleSendFriendRequest}
disabled={friendLoading}
className="flex items-center gap-2 px-5 py-2.5 rounded-full bg-vortex-500/20 border border-vortex-500/30 text-vortex-300 hover:bg-vortex-500/30 transition-all text-sm font-medium"
className="flex items-center gap-2 px-5 py-2.5 rounded-full bg-knot-500/20 border border-knot-500/30 text-knot-300 hover:bg-knot-500/30 transition-all text-sm font-medium"
>
{friendLoading ? <Loader2 size={16} className="animate-spin" /> : <UserPlus size={16} />}
{t('addFriend')}
@@ -602,10 +602,10 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
{/* О себе */}
<div className="bg-black/20 backdrop-blur-xl border border-white/5 rounded-2xl p-4 transition-all hover:bg-black/30 hover:border-white/10 group">
<div className="flex items-center gap-2 mb-2">
<div className="w-6 h-6 rounded-full bg-vortex-500/20 flex items-center justify-center border border-vortex-500/30">
<Edit3 size={12} className="text-vortex-400" />
<div className="w-6 h-6 rounded-full bg-knot-500/20 flex items-center justify-center border border-knot-500/30">
<Edit3 size={12} className="text-knot-400" />
</div>
<label className="text-xs font-semibold text-vortex-200/50 uppercase tracking-widest">
<label className="text-xs font-semibold text-knot-200/50 uppercase tracking-widest">
{t('aboutMe')}
</label>
</div>
@@ -614,7 +614,7 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
value={bio}
onChange={(e) => setBio(e.target.value)}
rows={3}
className="w-full rounded-xl bg-black/40 text-sm text-white placeholder-white/30 p-3 border border-white/10 focus:border-vortex-500 transition-colors resize-none outline-none leading-relaxed"
className="w-full rounded-xl bg-black/40 text-sm text-white placeholder-white/30 p-3 border border-white/10 focus:border-knot-500 transition-colors resize-none outline-none leading-relaxed"
placeholder={t('tellAboutYourself')}
/>
) : (
@@ -662,7 +662,7 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
<Check size={12} className="text-emerald-400" />
</div>
<label className="text-xs font-semibold text-emerald-200/50 uppercase tracking-widest">
{t('onVortexSince')}
{t('onKnotSince')}
</label>
</div>
<p className="text-sm text-zinc-200 pl-1">
@@ -715,7 +715,7 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
onClick={() => setActiveTab(tab.key)}
className={`flex-1 flex flex-col items-center justify-center gap-1 py-1 text-[10px] font-bold uppercase tracking-widest transition-all ${
activeTab === tab.key
? 'bg-white/5 text-vortex-400'
? 'bg-white/5 text-knot-400'
: 'text-zinc-500 hover:text-white/70'
}`}
>
@@ -832,8 +832,8 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
download={m.filename || 'file'}
className="flex items-center gap-3 px-4 py-3 hover:bg-white/5 transition-colors"
>
<div className="w-10 h-10 rounded-xl bg-vortex-500/20 flex items-center justify-center flex-shrink-0 border border-vortex-500/30 group-hover/file:scale-105 transition-transform">
<FileText size={18} className="text-vortex-400" />
<div className="w-10 h-10 rounded-xl bg-knot-500/20 flex items-center justify-center flex-shrink-0 border border-knot-500/30 group-hover/file:scale-105 transition-transform">
<FileText size={18} className="text-knot-400" />
</div>
<div className="flex-1 min-w-0">
<p className="text-sm text-white truncate">{m.filename || 'file'}</p>
@@ -872,7 +872,7 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
href={link}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 text-sm text-vortex-400 hover:text-vortex-300 transition-colors truncate"
className="flex items-center gap-2 text-sm text-knot-400 hover:text-knot-300 transition-colors truncate"
>
<ExternalLink size={14} className="flex-shrink-0" />
<span className="truncate">{link}</span>
@@ -883,7 +883,7 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
)}
<button
onClick={() => onGoToMessage?.(msg.id)}
className="absolute right-4 top-1/2 -translate-y-1/2 px-3 py-1.5 rounded-lg bg-black/40 hover:bg-vortex-500/20 text-zinc-300 hover:text-white text-[11px] font-medium opacity-0 group-hover:opacity-100 transition-all shadow-md z-10"
className="absolute right-4 top-1/2 -translate-y-1/2 px-3 py-1.5 rounded-lg bg-black/40 hover:bg-knot-500/20 text-zinc-300 hover:text-white text-[11px] font-medium opacity-0 group-hover:opacity-100 transition-all shadow-md z-10"
>
{t('showInChat')}
</button>
@@ -994,7 +994,7 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
step={0.1}
aria-labelledby="Zoom"
onChange={(e) => setZoom(Number(e.target.value))}
className="flex-1 accent-vortex-500"
className="flex-1 accent-knot-500"
/>
</div>
+11 -11
View File
@@ -3,17 +3,17 @@
@theme {
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
--color-vortex-50: #eef2ff;
--color-vortex-100: #e0e7ff;
--color-vortex-200: #c7d2fe;
--color-vortex-300: #a5b4fc;
--color-vortex-400: #818cf8;
--color-vortex-500: #6366f1;
--color-vortex-600: #4f46e5;
--color-vortex-700: #4338ca;
--color-vortex-800: #3730a3;
--color-vortex-900: #312e81;
--color-vortex-950: #1e1b4b;
--color-knot-50: #eef2ff;
--color-knot-100: #e0e7ff;
--color-knot-200: #c7d2fe;
--color-knot-300: #a5b4fc;
--color-knot-400: #818cf8;
--color-knot-500: #6366f1;
--color-knot-600: #4f46e5;
--color-knot-700: #4338ca;
--color-knot-800: #3730a3;
--color-knot-900: #312e81;
--color-knot-950: #1e1b4b;
--color-surface: #09090b;
--color-surface-secondary: #111113;
+4 -4
View File
@@ -2,7 +2,7 @@ import { create } from 'zustand';
export type Lang = 'ru' | 'en';
const LANG_KEY = 'vortex_language';
const LANG_KEY = 'knot_language';
const translations = {
ru: {
@@ -174,7 +174,7 @@ const translations = {
removeAvatar: 'Удалить аватар',
namePlaceholder: 'Имя',
notSpecified: 'Не указано',
onVortexSince: 'На SelfHost с',
onKnotSince: 'На Knot с',
cancel: 'Отмена',
confirm: 'Подтвердить',
save: 'Сохранить',
@@ -305,7 +305,7 @@ const translations = {
version: 'Version',
modernMessenger: 'A modern messenger focused',
onPrivacy: 'on privacy and convenience',
modernMessengerShort: 'SelfHost Messenger',
modernMessengerShort: 'Knot Messenger',
searchChats: 'Search chats...',
chat: 'Chat',
group: 'Group',
@@ -442,7 +442,7 @@ const translations = {
removeAvatar: 'Remove avatar',
namePlaceholder: 'Name',
notSpecified: 'Not specified',
onVortexSince: 'On SelfHost since',
onKnotSince: 'On Knot since',
cancel: 'Cancel',
confirm: 'Confirm',
save: 'Save',
+1 -1
View File
@@ -48,7 +48,7 @@ export function playNotificationSound() {
}
// Muted chats stored in localStorage
const MUTED_KEY = 'vortex_muted_chats';
const MUTED_KEY = 'knot_muted_chats';
export function getMutedChats(): Set<string> {
try {
+2 -2
View File
@@ -70,7 +70,7 @@ export default function AuthPage() {
<div className="absolute inset-0 rounded-[2rem] bg-gradient-to-br from-white/[0.05] to-transparent pointer-events-none" />
<MessageSquare className="w-10 h-10 text-accent drop-shadow-md relative z-10" />
</motion.div>
<h1 className="text-3xl font-bold gradient-text mt-6">SelfHost Messenger</h1>
<h1 className="text-3xl font-bold gradient-text mt-6">Knot Messenger</h1>
<p className="text-zinc-500 text-sm mt-2 tracking-wide uppercase">
{isLogin ? t('login') : t('register')}
</p>
@@ -127,7 +127,7 @@ export default function AuthPage() {
value={displayName}
onChange={(e) => setDisplayName(e.target.value)}
placeholder={t('displayNamePlaceholder')}
className="w-full px-4 py-3 rounded-xl bg-white/5 border border-white/10 text-white placeholder-zinc-600 focus:border-vortex-500/50 focus:ring-1 focus:ring-vortex-500/25 transition-all outline-hidden"
className="w-full px-4 py-3 rounded-xl bg-white/5 border border-white/10 text-white placeholder-zinc-600 focus:border-knot-500/50 focus:ring-1 focus:ring-knot-500/25 transition-all outline-hidden"
/>
</div>
</motion.div>
+5 -5
View File
@@ -16,7 +16,7 @@ interface AuthState {
}
export const useAuthStore = create<AuthState>((set, get) => ({
token: localStorage.getItem('vortex_token'),
token: localStorage.getItem('knot_token'),
user: null,
isLoading: true,
error: null,
@@ -25,7 +25,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
try {
set({ error: null, isLoading: true });
const { token, user } = await api.login(username, password);
localStorage.setItem('vortex_token', token);
localStorage.setItem('knot_token', token);
api.setToken(token);
connectSocket(token);
set({ token, user, isLoading: false });
@@ -40,7 +40,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
try {
set({ error: null, isLoading: true });
const { token, user } = await api.register(username, displayName, password, bio);
localStorage.setItem('vortex_token', token);
localStorage.setItem('knot_token', token);
api.setToken(token);
connectSocket(token);
set({ token, user, isLoading: false });
@@ -52,7 +52,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
},
logout: () => {
localStorage.removeItem('vortex_token');
localStorage.removeItem('knot_token');
api.setToken(null);
disconnectSocket();
set({ token: null, user: null });
@@ -87,7 +87,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
}
}
console.warn('checkAuth failed:', lastError);
localStorage.removeItem('vortex_token');
localStorage.removeItem('knot_token');
set({ token: null, user: null, isLoading: false });
},
+2 -2
View File
@@ -55,7 +55,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
isLoadingChats: false,
isLoadingMessages: false,
searchQuery: '',
drafts: JSON.parse(localStorage.getItem('vortex_drafts') || '{}'),
drafts: JSON.parse(localStorage.getItem('knot_drafts') || '{}'),
setActiveChat: (chatId) => set((state) => ({
activeChat: chatId,
@@ -75,7 +75,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
} else {
delete drafts[chatId];
}
localStorage.setItem('vortex_drafts', JSON.stringify(drafts));
localStorage.setItem('knot_drafts', JSON.stringify(drafts));
return { drafts };
});
},
+1 -1
View File
@@ -15,7 +15,7 @@ export const useThemeStore = create<ThemeState>()(
setChatTheme: (theme) => set({ chatTheme: theme }),
}),
{
name: 'vortex-theme-storage',
name: 'knot-theme-storage',
}
)
);