+ {memberSections.map((section) => (
+
+
{section.title} โ {section.count}
+ {section.members.map((member) => (
+
onMemberClick?.(member)}
+ style={{ cursor: 'pointer' }}
+ >
+
+ {member.initial}
+ {member.status && (
+
+ )}
-
{user.name}
- {user.activity &&
{user.activity}
}
+
{member.name}
+ {member.activity && (
+
{member.activity}
+ )}
))}
diff --git a/src/frontend/mockup/MessageActions.jsx b/src/frontend/mockup/MessageActions.jsx
new file mode 100644
index 0000000..964954a
--- /dev/null
+++ b/src/frontend/mockup/MessageActions.jsx
@@ -0,0 +1,84 @@
+import React, { useState } from 'react';
+
+export default function MessageActions({ message, onReact, onReply, onEdit, onDelete, onPin, onThread }) {
+ const [showMore, setShowMore] = useState(false);
+
+ const quickReactions = ['๐', 'โค๏ธ', '๐', '๐ฎ', '๐ข', '๐ก'];
+
+ return (
+
+
+ {quickReactions.map((emoji, idx) => (
+ onReact?.(emoji)}
+ title={`React with ${emoji}`}
+ >
+ {emoji}
+
+ ))}
+
+ ๐+
+
+
+
+
+ onReply?.()} title="Reply">
+ โฉ๏ธ
+
+ onThread?.()} title="Create Thread">
+ ๐งต
+
+ {message?.isOwn && (
+ onEdit?.()} title="Edit">
+ โ๏ธ
+
+ )}
+ setShowMore(!showMore)}
+ title="More"
+ >
+ โฏ
+
+
+
+ {showMore && (
+
+
onPin?.()}>
+ ๐ {message?.isPinned ? 'Unpin' : 'Pin'} Message
+
+
+ ๐ Copy Text
+
+
+ ๐ Copy Link
+
+
+ ๐ฉ Mark Unread
+
+
+
+ ๐ฌ Reply
+
+
+ ๐งต Create Thread
+
+
+
+ ๐ซ Report Message
+
+ {message?.isOwn && (
+ <>
+
+
onDelete?.()}>
+ ๐๏ธ Delete Message
+
+ >
+ )}
+
+ )}
+
+ );
+}
diff --git a/src/frontend/mockup/ModerationModal.jsx b/src/frontend/mockup/ModerationModal.jsx
new file mode 100644
index 0000000..267529e
--- /dev/null
+++ b/src/frontend/mockup/ModerationModal.jsx
@@ -0,0 +1,273 @@
+import React, { useState } from 'react';
+
+// Moderation action modal - for kicks, bans, timeouts
+export default function ModerationModal({ type, user, onSubmit, onClose }) {
+ const [reason, setReason] = useState('');
+ const [deleteMessages, setDeleteMessages] = useState('none');
+ const [duration, setDuration] = useState('1h');
+
+ const typeConfig = {
+ kick: {
+ title: 'Kick Member',
+ icon: '๐ข',
+ action: 'Kick',
+ color: '#faa61a',
+ description: `Are you sure you want to kick ${user?.name}? They will be able to rejoin with a new invite.`,
+ },
+ ban: {
+ title: 'Ban Member',
+ icon: '๐จ',
+ action: 'Ban',
+ color: '#ed4245',
+ description: `Are you sure you want to ban ${user?.name}? They will not be able to rejoin unless unbanned.`,
+ showDeleteMessages: true,
+ },
+ timeout: {
+ title: 'Timeout Member',
+ icon: 'โฐ',
+ action: 'Timeout',
+ color: '#faa61a',
+ description: `Temporarily mute ${user?.name}. They won't be able to send messages, react, or join voice.`,
+ showDuration: true,
+ },
+ warn: {
+ title: 'Warn Member',
+ icon: 'โ ๏ธ',
+ action: 'Warn',
+ color: '#faa61a',
+ description: `Send a warning to ${user?.name}. This will be logged in the audit log.`,
+ },
+ };
+
+ const config = typeConfig[type] || typeConfig.kick;
+
+ const durations = [
+ { value: '60s', label: '60 seconds' },
+ { value: '5m', label: '5 minutes' },
+ { value: '10m', label: '10 minutes' },
+ { value: '1h', label: '1 hour' },
+ { value: '1d', label: '1 day' },
+ { value: '1w', label: '1 week' },
+ ];
+
+ const deleteOptions = [
+ { value: 'none', label: "Don't delete any" },
+ { value: '1h', label: 'Previous hour' },
+ { value: '24h', label: 'Previous 24 hours' },
+ { value: '7d', label: 'Previous 7 days' },
+ ];
+
+ const handleSubmit = () => {
+ onSubmit?.({
+ type,
+ userId: user?.id,
+ reason,
+ deleteMessages: config.showDeleteMessages ? deleteMessages : undefined,
+ duration: config.showDuration ? duration : undefined,
+ });
+ };
+
+ return (
+
+
e.stopPropagation()}>
+
+ {config.icon}
+
{config.title}
+
+
+
+
+
+ {user?.avatar || user?.name?.[0]}
+
+
+ {user?.name}
+ {user?.tag && {user.tag} }
+
+
+
+
{config.description}
+
+ {config.showDuration && (
+
+ Duration
+ setDuration(e.target.value)}
+ className="mod-select"
+ >
+ {durations.map(d => (
+ {d.label}
+ ))}
+
+
+ )}
+
+ {config.showDeleteMessages && (
+
+ Delete message history
+ setDeleteMessages(e.target.value)}
+ className="mod-select"
+ >
+ {deleteOptions.map(d => (
+ {d.label}
+ ))}
+
+
+ )}
+
+
+ Reason (optional)
+
+
+
+
+ Cancel
+
+ {config.action}
+
+
+
+
+ );
+}
+
+// User notes modal
+export function UserNotesModal({ user, onSave, onClose }) {
+ const [note, setNote] = useState(user?.note || '');
+
+ return (
+
+
e.stopPropagation()}>
+
+
Note about {user?.name}
+ โ
+
+
+
+
+
+
+ Cancel
+ onSave?.(note)}
+ >
+ Save Note
+
+
+
+
+ );
+}
+
+// Report user modal
+export function ReportModal({ user, message, onSubmit, onClose }) {
+ const [category, setCategory] = useState('');
+ const [details, setDetails] = useState('');
+
+ const categories = [
+ { value: 'harassment', label: 'Harassment or Bullying' },
+ { value: 'spam', label: 'Spam or Scam' },
+ { value: 'hate', label: 'Hate Speech or Discrimination' },
+ { value: 'nsfw', label: 'Inappropriate Content' },
+ { value: 'impersonation', label: 'Impersonation' },
+ { value: 'threats', label: 'Threats or Violence' },
+ { value: 'other', label: 'Other' },
+ ];
+
+ return (
+
+
e.stopPropagation()}>
+
+ ๐ฉ
+
Report {message ? 'Message' : 'User'}
+
+
+
+ {user && (
+
+
+ {user.avatar || user.name?.[0]}
+
+
{user.name}
+
+ )}
+
+ {message && (
+
+ )}
+
+
+
What's the issue?
+
+ {categories.map(cat => (
+ setCategory(cat.value)}
+ >
+ {cat.label}
+
+ ))}
+
+
+
+
+ Additional details (optional)
+
+
+
+
โน๏ธ
+
Reports are confidential. The reported user won't know who reported them.
+
+
+
+
+ Cancel
+ onSubmit?.({ category, details })}
+ disabled={!category}
+ >
+ Submit Report
+
+
+
+
+ );
+}
diff --git a/src/frontend/mockup/NitroPanel.jsx b/src/frontend/mockup/NitroPanel.jsx
new file mode 100644
index 0000000..2b4e6d3
--- /dev/null
+++ b/src/frontend/mockup/NitroPanel.jsx
@@ -0,0 +1,233 @@
+import React, { useState } from 'react';
+
+const nitroFeatures = [
+ { id: 'animated-avatar', icon: '๐ค', title: 'Animated Avatar', description: 'Use GIFs as your profile picture' },
+ { id: 'animated-emoji', icon: '๐', title: 'Animated Emoji', description: 'Use animated emojis anywhere' },
+ { id: 'larger-uploads', icon: '๐', title: '500MB Uploads', description: 'Share large files up to 500MB' },
+ { id: 'hd-streaming', icon: '๐บ', title: 'HD Streaming', description: 'Stream in 4K at 60fps' },
+ { id: 'custom-tag', icon: '๐ท๏ธ', title: 'Custom Tag', description: 'Choose your own 4-digit tag' },
+ { id: 'server-boost', icon: '๐', title: '2 Free Boosts', description: 'Boost your favorite servers' },
+ { id: 'profile-banner', icon: '๐ผ๏ธ', title: 'Profile Banner', description: 'Add a custom banner to your profile' },
+ { id: 'custom-themes', icon: '๐จ', title: 'Custom Themes', description: 'Personalize your client' },
+ { id: 'super-reactions', icon: 'โจ', title: 'Super Reactions', description: 'Send enhanced animated reactions' },
+ { id: 'soundboard', icon: '๐', title: 'More Sounds', description: 'Upload more soundboard sounds' },
+];
+
+const plans = [
+ {
+ id: 'nitro',
+ name: 'Nitro',
+ price: '$9.99',
+ period: '/month',
+ yearlyPrice: '$99.99',
+ features: nitroFeatures.map(f => f.id),
+ popular: true,
+ },
+ {
+ id: 'nitro-basic',
+ name: 'Nitro Basic',
+ price: '$2.99',
+ period: '/month',
+ yearlyPrice: '$29.99',
+ features: ['animated-emoji', 'larger-uploads', 'custom-tag'],
+ popular: false,
+ },
+];
+
+const boostPerks = [
+ { level: 1, boosts: 2, perks: ['50 extra emoji slots', '128kbps audio', 'Animated server icon', 'Custom invite background'] },
+ { level: 2, boosts: 7, perks: ['100 extra emoji slots', '256kbps audio', '50MB upload limit', 'Server banner'] },
+ { level: 3, boosts: 14, perks: ['250 extra emoji slots', '384kbps audio', '100MB upload limit', 'Vanity URL'] },
+];
+
+export default function NitroPanel({ onClose }) {
+ const [activeTab, setActiveTab] = useState('subscribe');
+ const [billingCycle, setBillingCycle] = useState('monthly');
+ const [selectedPlan, setSelectedPlan] = useState('nitro');
+
+ return (
+
+
+
+ ๐
+
Nitro
+
+
โ
+
+
+
+ setActiveTab('subscribe')}
+ >
+ Subscribe
+
+ setActiveTab('boost')}
+ >
+ Server Boost
+
+ setActiveTab('gift')}
+ >
+ Gift
+
+
+
+ {activeTab === 'subscribe' && (
+
+
+
Unleash more fun
+
Subscribe to Nitro to unlock features and perks
+
+
+
+ setBillingCycle('monthly')}
+ >
+ Monthly
+
+ setBillingCycle('yearly')}
+ >
+ Yearly Save 16%
+
+
+
+
+ {plans.map(plan => (
+
setSelectedPlan(plan.id)}
+ >
+ {plan.popular &&
Most Popular
}
+
{plan.name}
+
+ {billingCycle === 'yearly' ? plan.yearlyPrice : plan.price}
+ {billingCycle === 'yearly' ? '/year' : plan.period}
+
+
+ {nitroFeatures.filter(f => plan.features.includes(f.id)).map(feature => (
+
+ โ
+ {feature.title}
+
+ ))}
+
+
+ {selectedPlan === plan.id ? 'Selected' : 'Select'}
+
+
+ ))}
+
+
+
+ Subscribe to {plans.find(p => p.id === selectedPlan)?.name}
+
+
+ )}
+
+ {activeTab === 'boost' && (
+
+
+
๐
+
Server Boost
+
Unlock perks for your favorite communities
+
+
+
+ {boostPerks.map(level => (
+
+
+ Level {level.level}
+ {level.boosts} Boosts
+
+
+ {level.perks.map((perk, idx) => (
+
+ โ
+ {perk}
+
+ ))}
+
+
+ ))}
+
+
+
+
+ ๐ Boost a Server
+
+
$4.99/month or included with Nitro
+
+
+ )}
+
+ {activeTab === 'gift' && (
+
+
+
๐
+
Gift Nitro
+
Send the gift of Nitro to a friend
+
+
+
+
+
Nitro
+
+ 1 Month - $9.99
+ 1 Year - $99.99
+
+
+
+
Nitro Basic
+
+ 1 Month - $2.99
+ 1 Year - $29.99
+
+
+
+
+
+ Send to
+
+
+
+
+ Add a message (optional)
+
+
+
+
+ ๐ Purchase Gift
+
+
+ )}
+
+
+
+ );
+}
+
+// Nitro badge component
+export function NitroBadge({ type = 'full' }) {
+ if (type === 'icon') {
+ return
๐ ;
+ }
+
+ return (
+
+ ๐
+ Nitro
+
+ );
+}
diff --git a/src/frontend/mockup/NotificationSettings.jsx b/src/frontend/mockup/NotificationSettings.jsx
new file mode 100644
index 0000000..81afd7e
--- /dev/null
+++ b/src/frontend/mockup/NotificationSettings.jsx
@@ -0,0 +1,158 @@
+import React, { useState } from 'react';
+
+export default function NotificationSettings({ server, channel, onClose, onSave }) {
+ const isChannelSettings = !!channel;
+ const name = channel?.name || server?.name || 'AeThex Foundation';
+
+ const [notifSetting, setNotifSetting] = useState('all');
+ const [suppressEveryone, setSuppressEveryone] = useState(false);
+ const [suppressRoles, setSuppressRoles] = useState(false);
+ const [muteUntil, setMuteUntil] = useState('none');
+ const [mobilePush, setMobilePush] = useState(true);
+
+ const notifOptions = [
+ { id: 'all', label: 'All Messages', desc: 'Get notified for all messages' },
+ { id: 'mentions', label: 'Only @mentions', desc: 'Only notify when mentioned' },
+ { id: 'nothing', label: 'Nothing', desc: 'Mute all notifications' },
+ ];
+
+ if (isChannelSettings) {
+ notifOptions.unshift({ id: 'default', label: 'Default', desc: 'Use server default setting' });
+ }
+
+ const muteOptions = [
+ { value: 'none', label: 'Not muted' },
+ { value: '15min', label: 'For 15 minutes' },
+ { value: '1hour', label: 'For 1 hour' },
+ { value: '8hours', label: 'For 8 hours' },
+ { value: '24hours', label: 'For 24 hours' },
+ { value: 'forever', label: 'Until I turn it back on' },
+ ];
+
+ const handleSave = () => {
+ onSave?.({
+ notifSetting,
+ suppressEveryone,
+ suppressRoles,
+ muteUntil,
+ mobilePush,
+ });
+ };
+
+ return (
+
+
e.stopPropagation()}>
+
+
๐ Notification Settings
+ โ
+
+
+
+ {isChannelSettings ? '#' : '๐ '}
+ {name}
+
+
+
+
+
Notification Frequency
+
+ {notifOptions.map(opt => (
+
+ setNotifSetting(opt.id)}
+ />
+
+ {opt.label}
+ {opt.desc}
+
+
+ ))}
+
+
+
+
+
Mute {isChannelSettings ? 'Channel' : 'Server'}
+ setMuteUntil(e.target.value)}
+ >
+ {muteOptions.map(opt => (
+ {opt.label}
+ ))}
+
+
+
+
+
+
+
+
+
Mobile Push Notifications
+
Receive push notifications on mobile
+
+
+ setMobilePush(e.target.checked)}
+ />
+
+
+
+
+
+ {!isChannelSettings && (
+
+
Channel Overrides
+
You can customize notifications for individual channels
+
+
+ #announcements
+ All Messages
+
+
+ #spam
+ Muted
+
+
+
+ Add Override
+
+ )}
+
+
+
+ Cancel
+ Done
+
+
+
+ );
+}
diff --git a/src/frontend/mockup/NotificationsPanel.jsx b/src/frontend/mockup/NotificationsPanel.jsx
new file mode 100644
index 0000000..aab9bea
--- /dev/null
+++ b/src/frontend/mockup/NotificationsPanel.jsx
@@ -0,0 +1,189 @@
+import React, { useState } from 'react';
+
+const allNotifications = [
+ {
+ id: 1,
+ type: 'mention',
+ server: 'AeThex Foundation',
+ serverIcon: 'F',
+ serverGradient: 'linear-gradient(135deg, #ff0000, #990000)',
+ channel: 'general',
+ author: 'Trevor',
+ authorAvatar: 'T',
+ message: '@Anderson check out the new auth flow!',
+ time: '2 minutes ago',
+ unread: true,
+ },
+ {
+ id: 2,
+ type: 'reply',
+ server: 'AeThex Labs',
+ serverIcon: 'L',
+ serverGradient: 'linear-gradient(135deg, #ffa500, #ff8c00)',
+ channel: 'testing',
+ author: 'Sarah',
+ authorAvatar: 'S',
+ message: 'Great point! I\'ll add that to the test suite.',
+ time: '15 minutes ago',
+ unread: true,
+ },
+ {
+ id: 3,
+ type: 'dm',
+ author: 'Marcus',
+ authorAvatar: 'M',
+ authorGradient: 'linear-gradient(135deg, #0066ff, #003380)',
+ message: 'The API integration is ready for review!',
+ time: '1 hour ago',
+ unread: true,
+ },
+ {
+ id: 4,
+ type: 'friend-request',
+ author: 'NewDev123',
+ authorAvatar: 'N',
+ message: 'sent you a friend request.',
+ time: '2 hours ago',
+ unread: false,
+ },
+ {
+ id: 5,
+ type: 'server-invite',
+ server: 'Dev Community',
+ serverIcon: 'DC',
+ author: 'JohnDev',
+ message: 'invited you to join the server.',
+ time: '3 hours ago',
+ unread: false,
+ },
+];
+
+export default function NotificationsPanel({ onClose, onNotificationClick }) {
+ const [filter, setFilter] = useState('all');
+ const [notifications, setNotifications] = useState(allNotifications);
+
+ const filters = [
+ { id: 'all', label: 'All' },
+ { id: 'mentions', label: 'Mentions' },
+ { id: 'unreads', label: 'Unreads' },
+ ];
+
+ const filteredNotifications = notifications.filter((n) => {
+ if (filter === 'mentions') return n.type === 'mention';
+ if (filter === 'unreads') return n.unread;
+ return true;
+ });
+
+ const markAllRead = () => {
+ setNotifications((prev) => prev.map((n) => ({ ...n, unread: false })));
+ };
+
+ const clearAll = () => {
+ setNotifications([]);
+ };
+
+ const getNotificationIcon = (type) => {
+ switch (type) {
+ case 'mention': return '@';
+ case 'reply': return 'โฉ๏ธ';
+ case 'dm': return '๐ฌ';
+ case 'friend-request': return '๐';
+ case 'server-invite': return 'โ๏ธ';
+ default: return '๐';
+ }
+ };
+
+ return (
+
+
+
Inbox
+
+ Mark All Read
+ Clear All
+
+
โ
+
+
+
+ {filters.map((f) => (
+ setFilter(f.id)}
+ >
+ {f.label}
+
+ ))}
+
+
+
+ {filteredNotifications.length === 0 ? (
+
+
๐ญ
+
You're all caught up!
+
+ ) : (
+ filteredNotifications.map((notification) => (
+
onNotificationClick?.(notification)}
+ >
+
+ {notification.type === 'dm' || notification.type === 'friend-request' ? (
+
+ {notification.authorAvatar}
+
+ ) : notification.serverIcon ? (
+
+ {notification.serverIcon}
+
+ ) : (
+
+ {getNotificationIcon(notification.type)}
+
+ )}
+
+ {getNotificationIcon(notification.type)}
+
+
+
+
+
+ {notification.server && (
+ <>
+ {notification.server}
+ โข
+ #{notification.channel}
+ >
+ )}
+ {notification.type === 'dm' && (
+ {notification.author}
+ )}
+ {notification.type === 'friend-request' && (
+ {notification.author}
+ )}
+
+
+ {notification.author && notification.type !== 'dm' && notification.type !== 'friend-request' && (
+ {notification.author}:
+ )}
+ {notification.message}
+
+
{notification.time}
+
+
+ {notification.unread &&
}
+
+ ))
+ )}
+
+
+ );
+}
diff --git a/src/frontend/mockup/PinnedMessagesPanel.jsx b/src/frontend/mockup/PinnedMessagesPanel.jsx
new file mode 100644
index 0000000..3b9a282
--- /dev/null
+++ b/src/frontend/mockup/PinnedMessagesPanel.jsx
@@ -0,0 +1,92 @@
+import React from 'react';
+
+const pinnedMessages = [
+ {
+ id: 1,
+ author: 'Anderson',
+ avatar: 'A',
+ gradient: 'linear-gradient(135deg, #ff0000, #0066ff, #ffa500)',
+ badge: 'Founder',
+ time: 'Jan 15, 2026',
+ text: 'Welcome to AeThex Connect! Please read the rules in #rules and introduce yourself in #introductions. The Trinity awaits! ๐ฅ',
+ pinnedBy: 'Anderson',
+ },
+ {
+ id: 2,
+ author: 'Trevor',
+ avatar: 'T',
+ gradient: 'linear-gradient(135deg, #ff0000, #cc0000)',
+ badge: 'Foundation',
+ time: 'Jan 20, 2026',
+ text: 'Important: Authentication v2.1.0 is now live! All users should update their Passport credentials. Check #updates for migration guide.',
+ pinnedBy: 'Trevor',
+ },
+ {
+ id: 3,
+ author: 'Sarah',
+ avatar: 'S',
+ gradient: 'linear-gradient(135deg, #ffa500, #ff8c00)',
+ badge: 'Labs',
+ time: 'Feb 1, 2026',
+ text: 'Nexus Engine v2.0 is officially out of beta! Huge thanks to everyone who helped test. Read the changelog in #changelog ๐',
+ pinnedBy: 'Sarah',
+ },
+];
+
+export default function PinnedMessagesPanel({ channelName, onClose, onJumpToMessage }) {
+ return (
+
+
+ ๐
+ Pinned Messages
+ #{channelName || 'general'}
+ โ
+
+
+
+ {pinnedMessages.length === 0 ? (
+
+
๐
+
This channel doesn't have any pinned messages... yet.
+
+ ) : (
+
+ {pinnedMessages.map((msg) => (
+
+
+
+ {msg.avatar}
+
+
+ {msg.author}
+ {msg.badge && {msg.badge} }
+ {msg.time}
+
+
+
{msg.text}
+
+ Pinned by {msg.pinnedBy}
+ onJumpToMessage?.(msg)}
+ >
+ Jump
+
+
+
+ ))}
+
+ )}
+
+
+
+
+ PROTIP: You can pin up to 50 messages in a channel.
+
+
+
+ );
+}
diff --git a/src/frontend/mockup/PollCreator.jsx b/src/frontend/mockup/PollCreator.jsx
new file mode 100644
index 0000000..6947cc6
--- /dev/null
+++ b/src/frontend/mockup/PollCreator.jsx
@@ -0,0 +1,234 @@
+import React, { useState } from 'react';
+
+export default function PollCreator({ onSubmit, onClose }) {
+ const [question, setQuestion] = useState('');
+ const [options, setOptions] = useState(['', '']);
+ const [duration, setDuration] = useState('24h');
+ const [multiSelect, setMultiSelect] = useState(false);
+
+ const durations = [
+ { value: '1h', label: '1 Hour' },
+ { value: '4h', label: '4 Hours' },
+ { value: '8h', label: '8 Hours' },
+ { value: '24h', label: '24 Hours' },
+ { value: '3d', label: '3 Days' },
+ { value: '7d', label: '1 Week' },
+ ];
+
+ const addOption = () => {
+ if (options.length < 10) {
+ setOptions([...options, '']);
+ }
+ };
+
+ const removeOption = (index) => {
+ if (options.length > 2) {
+ setOptions(options.filter((_, i) => i !== index));
+ }
+ };
+
+ const updateOption = (index, value) => {
+ setOptions(options.map((opt, i) => i === index ? value : opt));
+ };
+
+ const handleSubmit = () => {
+ if (!question.trim() || options.filter(o => o.trim()).length < 2) return;
+
+ onSubmit?.({
+ question,
+ options: options.filter(o => o.trim()),
+ duration,
+ multiSelect,
+ });
+ onClose?.();
+ };
+
+ const validOptions = options.filter(o => o.trim()).length;
+
+ return (
+
+
e.stopPropagation()}>
+
+
Create Poll
+ โ
+
+
+
+
+ Question
+
+
+
+
Answers
+
+ {options.map((opt, idx) => (
+
+ {idx + 1}
+ updateOption(idx, e.target.value)}
+ maxLength={55}
+ />
+ {options.length > 2 && (
+ removeOption(idx)}
+ >
+ โ
+
+ )}
+
+ ))}
+
+ {options.length < 10 && (
+
+ + Add another answer
+
+ )}
+
+
+
+ Duration
+ setDuration(e.target.value)}
+ className="poll-select"
+ >
+ {durations.map(d => (
+ {d.label}
+ ))}
+
+
+
+
+
+ setMultiSelect(e.target.checked)}
+ />
+
+ Allow multiple answers
+
+
+
+
+
+
Preview
+
+
{question || 'Your question here'}
+
+ {options.filter(o => o.trim()).map((opt, idx) => (
+
+ ))}
+
+
+ 0 votes
+ โข
+ {durations.find(d => d.value === duration)?.label} remaining
+
+
+
+
+
+ Cancel
+
+ Create Poll
+
+
+
+
+ );
+}
+
+// Poll display component for messages
+export function Poll({ poll, onVote }) {
+ const [selected, setSelected] = useState([]);
+ const [hasVoted, setHasVoted] = useState(false);
+
+ const totalVotes = poll.options.reduce((sum, opt) => sum + (opt.votes || 0), 0);
+
+ const handleVote = (optionIndex) => {
+ if (hasVoted) return;
+
+ if (poll.multiSelect) {
+ setSelected(prev =>
+ prev.includes(optionIndex)
+ ? prev.filter(i => i !== optionIndex)
+ : [...prev, optionIndex]
+ );
+ } else {
+ setSelected([optionIndex]);
+ }
+ };
+
+ const submitVote = () => {
+ if (selected.length === 0) return;
+ onVote?.(selected);
+ setHasVoted(true);
+ };
+
+ return (
+
+
{poll.question}
+
+ {poll.options.map((opt, idx) => {
+ const percentage = hasVoted && totalVotes > 0
+ ? Math.round((opt.votes / totalVotes) * 100)
+ : 0;
+
+ return (
+
handleVote(idx)}
+ disabled={hasVoted}
+ >
+ {hasVoted && (
+
+ )}
+
+ {!hasVoted && (
+
+ {selected.includes(idx) && 'โ'}
+
+ )}
+
{opt.text}
+ {hasVoted && (
+
{percentage}%
+ )}
+
+
+ );
+ })}
+
+ {!hasVoted && selected.length > 0 && (
+
+ Vote
+
+ )}
+
+ {totalVotes} vote{totalVotes !== 1 ? 's' : ''}
+ โข
+ {poll.timeLeft}
+
+
+ );
+}
diff --git a/src/frontend/mockup/QuickSwitcher.jsx b/src/frontend/mockup/QuickSwitcher.jsx
new file mode 100644
index 0000000..ff94372
--- /dev/null
+++ b/src/frontend/mockup/QuickSwitcher.jsx
@@ -0,0 +1,216 @@
+import React, { useState, useEffect, useCallback } from 'react';
+
+const recentItems = [
+ { type: 'channel', id: 'general', name: 'general', server: 'AeThex Foundation', icon: '#' },
+ { type: 'dm', id: 'dm-1', name: 'Trevor', avatar: 'T', color: '#ff0000' },
+ { type: 'channel', id: 'api-discussion', name: 'api-discussion', server: 'AeThex Foundation', icon: '#' },
+];
+
+const allItems = [
+ // Channels
+ { type: 'channel', id: 'general', name: 'general', server: 'AeThex Foundation', icon: '#' },
+ { type: 'channel', id: 'updates', name: 'updates', server: 'AeThex Foundation', icon: '๐ข' },
+ { type: 'channel', id: 'api-discussion', name: 'api-discussion', server: 'AeThex Foundation', icon: '#' },
+ { type: 'channel', id: 'bug-reports', name: 'bug-reports', server: 'AeThex Foundation', icon: '๐' },
+ { type: 'channel', id: 'nexus-lounge', name: 'Nexus Lounge', server: 'AeThex Foundation', icon: '๐', isVoice: true },
+ { type: 'channel', id: 'labs-general', name: 'general', server: 'AeThex Labs', icon: '#' },
+ { type: 'channel', id: 'experiments', name: 'experiments', server: 'AeThex Labs', icon: '๐งช' },
+ // Users/DMs
+ { type: 'user', id: 'u-1', name: 'Trevor', tag: '#0001', avatar: 'T', color: '#ff0000' },
+ { type: 'user', id: 'u-2', name: 'Sarah', tag: '#0042', avatar: 'S', color: '#ffa500' },
+ { type: 'user', id: 'u-3', name: 'Marcus', tag: '#1234', avatar: 'M', color: '#0066ff' },
+ { type: 'user', id: 'u-4', name: 'Anderson', tag: '#0001', avatar: 'A', color: '#5865f2' },
+ // Servers
+ { type: 'server', id: 's-1', name: 'AeThex Foundation', icon: 'F', color: '#ff0000' },
+ { type: 'server', id: 's-2', name: 'AeThex Labs', icon: 'L', color: '#ffa500' },
+ { type: 'server', id: 's-3', name: 'AeThex Corporation', icon: 'C', color: '#0066ff' },
+];
+
+export default function QuickSwitcher({ onSelect, onClose }) {
+ const [query, setQuery] = useState('');
+ const [selectedIndex, setSelectedIndex] = useState(0);
+ const [filter, setFilter] = useState('all'); // all, channels, users, servers
+
+ const getFilteredItems = useCallback(() => {
+ let items = query ? allItems : recentItems;
+
+ if (query) {
+ const lowerQuery = query.toLowerCase();
+ items = items.filter(item =>
+ item.name.toLowerCase().includes(lowerQuery) ||
+ (item.server && item.server.toLowerCase().includes(lowerQuery))
+ );
+ }
+
+ if (filter !== 'all') {
+ const typeMap = {
+ channels: 'channel',
+ users: ['user', 'dm'],
+ servers: 'server',
+ };
+ const types = typeMap[filter];
+ items = items.filter(item =>
+ Array.isArray(types) ? types.includes(item.type) : item.type === types
+ );
+ }
+
+ return items.slice(0, 10);
+ }, [query, filter]);
+
+ const filteredItems = getFilteredItems();
+
+ useEffect(() => {
+ setSelectedIndex(0);
+ }, [query, filter]);
+
+ const handleKeyDown = useCallback((e) => {
+ switch (e.key) {
+ case 'ArrowDown':
+ e.preventDefault();
+ setSelectedIndex(prev => Math.min(prev + 1, filteredItems.length - 1));
+ break;
+ case 'ArrowUp':
+ e.preventDefault();
+ setSelectedIndex(prev => Math.max(prev - 1, 0));
+ break;
+ case 'Enter':
+ e.preventDefault();
+ if (filteredItems[selectedIndex]) {
+ onSelect?.(filteredItems[selectedIndex]);
+ onClose?.();
+ }
+ break;
+ case 'Escape':
+ e.preventDefault();
+ onClose?.();
+ break;
+ case 'Tab':
+ e.preventDefault();
+ // Cycle through filters
+ const filters = ['all', 'channels', 'users', 'servers'];
+ const currentIdx = filters.indexOf(filter);
+ setFilter(filters[(currentIdx + 1) % filters.length]);
+ break;
+ }
+ }, [filteredItems, selectedIndex, filter, onSelect, onClose]);
+
+ useEffect(() => {
+ document.addEventListener('keydown', handleKeyDown);
+ return () => document.removeEventListener('keydown', handleKeyDown);
+ }, [handleKeyDown]);
+
+ const getItemIcon = (item) => {
+ if (item.type === 'channel') {
+ return item.isVoice ? '๐' : item.icon;
+ }
+ if (item.type === 'user' || item.type === 'dm') {
+ return (
+
+ {item.avatar}
+
+ );
+ }
+ if (item.type === 'server') {
+ return (
+
+ {item.icon}
+
+ );
+ }
+ };
+
+ return (
+
+
e.stopPropagation()}>
+
+ setQuery(e.target.value)}
+ autoFocus
+ />
+
+
+
+ setFilter('all')}
+ >
+ All
+
+ setFilter('channels')}
+ >
+ # Channels
+
+ setFilter('users')}
+ >
+ @ Users
+
+ setFilter('servers')}
+ >
+ ๐ Servers
+
+
+
+
+ {!query && (
+
Recent
+ )}
+ {filteredItems.map((item, idx) => (
+
{
+ onSelect?.(item);
+ onClose?.();
+ }}
+ onMouseEnter={() => setSelectedIndex(idx)}
+ >
+
{getItemIcon(item)}
+
+ {item.name}
+ {item.tag && {item.tag} }
+ {item.server && {item.server} }
+
+ {item.type === 'channel' &&
# }
+ {item.type === 'user' &&
@ }
+
+ ))}
+ {filteredItems.length === 0 && (
+
+ )}
+
+
+
+
+ โ โ to navigate
+
+
+ Enter to select
+
+
+ Tab to filter
+
+
+ Esc to close
+
+
+
+
+ );
+}
diff --git a/src/frontend/mockup/ReplyPreview.jsx b/src/frontend/mockup/ReplyPreview.jsx
new file mode 100644
index 0000000..79b310d
--- /dev/null
+++ b/src/frontend/mockup/ReplyPreview.jsx
@@ -0,0 +1,54 @@
+import React from 'react';
+
+export default function ReplyPreview({ message, onCancel, onClose }) {
+ if (!message) return null;
+
+ return (
+
+
+
+ Replying to
+
+ {message.author}
+
+ {message.text?.substring(0, 100)}{message.text?.length > 100 ? '...' : ''}
+
+
โ
+
+ );
+}
+
+export function MessageWithReply({ message, replyTo }) {
+ if (!replyTo) return null;
+
+ return (
+
+
+
+ {replyTo.avatar?.initial || replyTo.author?.charAt(0)}
+
+
+ {replyTo.author}
+
+
+ {replyTo.text?.substring(0, 50)}{replyTo.text?.length > 50 ? '...' : ''}
+
+
+ );
+}
diff --git a/src/frontend/mockup/RoleEditor.jsx b/src/frontend/mockup/RoleEditor.jsx
new file mode 100644
index 0000000..4c22067
--- /dev/null
+++ b/src/frontend/mockup/RoleEditor.jsx
@@ -0,0 +1,272 @@
+import React, { useState } from 'react';
+
+const permissions = [
+ { category: 'General', items: [
+ { id: 'view_channels', label: 'View Channels', description: 'Members can view channels by default' },
+ { id: 'manage_channels', label: 'Manage Channels', description: 'Create, edit, and delete channels', dangerous: true },
+ { id: 'manage_roles', label: 'Manage Roles', description: 'Create and edit roles below this one', dangerous: true },
+ { id: 'manage_emoji', label: 'Manage Emoji & Stickers', description: 'Add, remove, and manage custom emojis' },
+ { id: 'view_audit', label: 'View Audit Log', description: 'View a log of all moderation actions' },
+ { id: 'manage_webhooks', label: 'Manage Webhooks', description: 'Create, edit, and delete webhooks' },
+ { id: 'manage_server', label: 'Manage Server', description: 'Change server name, region, etc.', dangerous: true },
+ ]},
+ { category: 'Membership', items: [
+ { id: 'create_invite', label: 'Create Invite', description: 'Invite new people to this server' },
+ { id: 'change_nickname', label: 'Change Nickname', description: 'Change their own nickname' },
+ { id: 'manage_nicknames', label: 'Manage Nicknames', description: 'Change nicknames of other members' },
+ { id: 'kick_members', label: 'Kick Members', description: 'Remove members from the server', dangerous: true },
+ { id: 'ban_members', label: 'Ban Members', description: 'Permanently ban members', dangerous: true },
+ { id: 'timeout_members', label: 'Timeout Members', description: 'Temporarily mute members' },
+ ]},
+ { category: 'Text', items: [
+ { id: 'send_messages', label: 'Send Messages', description: 'Send messages in text channels' },
+ { id: 'send_tts', label: 'Send TTS Messages', description: 'Send text-to-speech messages' },
+ { id: 'manage_messages', label: 'Manage Messages', description: 'Delete and pin messages from others', dangerous: true },
+ { id: 'embed_links', label: 'Embed Links', description: 'Links will show a preview' },
+ { id: 'attach_files', label: 'Attach Files', description: 'Upload images and files' },
+ { id: 'add_reactions', label: 'Add Reactions', description: 'React to messages with emoji' },
+ { id: 'use_external_emoji', label: 'Use External Emoji', description: 'Use emoji from other servers' },
+ { id: 'mention_everyone', label: 'Mention @everyone', description: 'Can use @everyone and @here', dangerous: true },
+ ]},
+ { category: 'Voice', items: [
+ { id: 'connect', label: 'Connect', description: 'Join voice channels' },
+ { id: 'speak', label: 'Speak', description: 'Talk in voice channels' },
+ { id: 'video', label: 'Video', description: 'Share camera in voice channels' },
+ { id: 'mute_members', label: 'Mute Members', description: 'Mute other members' },
+ { id: 'deafen_members', label: 'Deafen Members', description: 'Deafen other members' },
+ { id: 'move_members', label: 'Move Members', description: 'Move members between voice channels' },
+ { id: 'use_vad', label: 'Use Voice Activity', description: 'Use voice activity detection' },
+ { id: 'priority_speaker', label: 'Priority Speaker', description: 'Be more easily heard' },
+ ]},
+];
+
+export default function RoleEditor({ role, onSave, onClose }) {
+ const [roleName, setRoleName] = useState(role?.name || 'New Role');
+ const [roleColor, setRoleColor] = useState(role?.color || '#99aab5');
+ const [rolePermissions, setRolePermissions] = useState(role?.permissions || {});
+ const [displaySeparately, setDisplaySeparately] = useState(role?.displaySeparately || false);
+ const [mentionable, setMentionable] = useState(role?.mentionable || false);
+ const [activeTab, setActiveTab] = useState('display');
+
+ const colors = [
+ '#99aab5', '#1abc9c', '#2ecc71', '#3498db', '#9b59b6',
+ '#e91e63', '#f1c40f', '#e67e22', '#e74c3c', '#95a5a6',
+ '#607d8b', '#11806a', '#1f8b4c', '#206694', '#71368a',
+ '#ad1457', '#c27c0e', '#a84300', '#992d22', '#979c9f',
+ ];
+
+ const togglePermission = (permId) => {
+ setRolePermissions(prev => ({
+ ...prev,
+ [permId]: prev[permId] === true ? false : prev[permId] === false ? null : true
+ }));
+ };
+
+ const getPermState = (permId) => {
+ const state = rolePermissions[permId];
+ if (state === true) return 'granted';
+ if (state === false) return 'denied';
+ return 'inherit';
+ };
+
+ return (
+
+
+
+
+
setRoleName(e.target.value)}
+ className="role-name-input"
+ />
+
+
+
+ setActiveTab('display')}
+ >
+ Display
+
+ setActiveTab('permissions')}
+ >
+ Permissions
+
+ setActiveTab('members')}
+ >
+ Members
+
+
+
+
+
+
+
Edit Role โ {roleName}
+ โ
+
+
+ {activeTab === 'display' && (
+
+
+ Role Name
+ setRoleName(e.target.value)}
+ className="text-input"
+ />
+
+
+
+
Role Color
+
+ {colors.map(color => (
+ setRoleColor(color)}
+ />
+ ))}
+
+
+ setRoleColor(e.target.value)}
+ />
+ setRoleColor(e.target.value)}
+ className="color-text-input"
+ />
+
+
+
+
+
+
+ Display role members separately
+ Show members with this role in a separate category
+
+ setDisplaySeparately(e.target.checked)}
+ />
+
+
+
+
+
+
+ Allow anyone to @mention this role
+ Members can mention this role in chat
+
+ setMentionable(e.target.checked)}
+ />
+
+
+
+
+
Role Icon
+
+
๐ญ
+
Upload Image
+
Recommended 64x64
+
+
+
+ )}
+
+ {activeTab === 'permissions' && (
+
+
+ โ ๏ธ Be careful! Granting dangerous permissions can allow members to harm your server.
+
+
+ {permissions.map(category => (
+
+
{category.category}
+ {category.items.map(perm => (
+
+
+ {perm.label}
+ {perm.description}
+
+
+ setRolePermissions(prev => ({ ...prev, [perm.id]: false }))}
+ >
+ โ
+
+ setRolePermissions(prev => ({ ...prev, [perm.id]: null }))}
+ >
+ /
+
+ setRolePermissions(prev => ({ ...prev, [perm.id]: true }))}
+ >
+ โ
+
+
+
+ ))}
+
+ ))}
+
+ )}
+
+ {activeTab === 'members' && (
+
+
+
+ + Add Members
+
+
+
+ )}
+
+
+
Delete Role
+
+ Cancel
+ onSave?.({ name: roleName, color: roleColor, permissions: rolePermissions, displaySeparately, mentionable })}
+ >
+ Save Changes
+
+
+
+
+
+ );
+}
diff --git a/src/frontend/mockup/SearchPanel.jsx b/src/frontend/mockup/SearchPanel.jsx
new file mode 100644
index 0000000..acc7f19
--- /dev/null
+++ b/src/frontend/mockup/SearchPanel.jsx
@@ -0,0 +1,176 @@
+import React, { useState } from 'react';
+
+export default function SearchPanel({ onClose, onSearch }) {
+ const [query, setQuery] = useState('');
+ const [filter, setFilter] = useState('all');
+ const [results, setResults] = useState([]);
+ const [isSearching, setIsSearching] = useState(false);
+
+ const filters = [
+ { id: 'all', label: 'All', icon: '๐' },
+ { id: 'messages', label: 'Messages', icon: '๐ฌ' },
+ { id: 'files', label: 'Files', icon: '๐' },
+ { id: 'links', label: 'Links', icon: '๐' },
+ { id: 'images', label: 'Images', icon: '๐ผ๏ธ' },
+ { id: 'mentions', label: 'Mentions', icon: '@' },
+ { id: 'pinned', label: 'Pinned', icon: '๐' },
+ ];
+
+ const recentSearches = [
+ 'authentication update',
+ 'from: Anderson',
+ 'has: link',
+ 'in: general',
+ 'before: 2026-01-15',
+ ];
+
+ const mockResults = [
+ {
+ type: 'message',
+ channel: '#general',
+ author: 'Trevor',
+ avatar: 'T',
+ gradient: 'linear-gradient(135deg, #ff0000, #cc0000)',
+ content: 'Just pushed the authentication updates...',
+ timestamp: '10:34 AM',
+ highlight: 'authentication',
+ },
+ {
+ type: 'message',
+ channel: '#api-discussion',
+ author: 'Marcus',
+ avatar: 'M',
+ gradient: 'linear-gradient(135deg, #0066ff, #003380)',
+ content: 'The new auth flow is working perfectly now',
+ timestamp: 'Yesterday',
+ highlight: 'auth',
+ },
+ {
+ type: 'file',
+ channel: '#development',
+ author: 'Sarah',
+ filename: 'auth-documentation.pdf',
+ size: '2.4 MB',
+ timestamp: 'Jan 15',
+ },
+ ];
+
+ const handleSearch = (e) => {
+ e.preventDefault();
+ if (!query.trim()) return;
+ setIsSearching(true);
+ // Simulate search
+ setTimeout(() => {
+ setResults(mockResults);
+ setIsSearching(false);
+ }, 500);
+ };
+
+ return (
+
+
+
+ โ
+
+
+
+ {filters.map(f => (
+ setFilter(f.id)}
+ >
+ {f.icon}
+ {f.label}
+
+ ))}
+
+
+
+ {!query && (
+
+
Recent Searches
+ {recentSearches.map((search, idx) => (
+
setQuery(search)}
+ >
+ ๐
+ {search}
+
+ ))}
+
+
Search Options
+
+
from: user
+
mentions: user
+
has: link, embed, file
+
before: date
+
after: date
+
in: channel
+
+
+ )}
+
+ {isSearching && (
+
+ )}
+
+ {results.length > 0 && !isSearching && (
+
+
{results.length} results found
+ {results.map((result, idx) => (
+
+ {result.type === 'message' && (
+ <>
+
+ {result.avatar}
+
+
+
+ {result.author}
+ {result.channel}
+ {result.timestamp}
+
+
{result.content}
+
+ >
+ )}
+ {result.type === 'file' && (
+ <>
+
๐
+
+
{result.filename}
+
+ {result.size} โข {result.channel} โข {result.timestamp}
+
+
+ >
+ )}
+
+ ))}
+
+ )}
+
+
+ );
+}
diff --git a/src/frontend/mockup/ServerBanner.jsx b/src/frontend/mockup/ServerBanner.jsx
new file mode 100644
index 0000000..5ae158e
--- /dev/null
+++ b/src/frontend/mockup/ServerBanner.jsx
@@ -0,0 +1,133 @@
+import React from 'react';
+
+export default function ServerBanner({ server, isCollapsed }) {
+ const serverData = server || {
+ name: 'AeThex Foundation',
+ icon: 'F',
+ gradient: 'linear-gradient(135deg, #ff0000, #990000)',
+ banner: 'linear-gradient(180deg, #ff0000 0%, transparent 100%)',
+ description: 'Official AeThex Foundation server',
+ boostLevel: 3,
+ boostCount: 14,
+ memberCount: 128,
+ onlineCount: 47,
+ features: ['ANIMATED_BANNER', 'ANIMATED_ICON', 'BANNER', 'INVITE_SPLASH', 'VANITY_URL', 'VIP_REGIONS'],
+ vanityUrl: 'aethex',
+ };
+
+ const boostFeatures = [
+ { level: 1, features: ['50 Emoji Slots', '128 Kbps Audio', 'Custom Invite Background', 'Animated Server Icon'] },
+ { level: 2, features: ['100 Emoji Slots', '256 Kbps Audio', 'Server Banner', '50 MB Uploads', 'Custom Role Icons'] },
+ { level: 3, features: ['250 Emoji Slots', '384 Kbps Audio', 'Vanity URL', '100 MB Uploads', 'Animated Banner'] },
+ ];
+
+ if (isCollapsed) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+
+
{serverData.name}
+
{serverData.description}
+
+
+
+
+
+
+
+ ๐ฅ
+ {serverData.memberCount}
+ Members
+
+
+
+ {serverData.onlineCount}
+ Online
+
+
+
+
+ ๐
+ Level {serverData.boostLevel}
+ {serverData.boostCount} Boosts
+
+
+
+ {serverData.boostLevel > 0 && (
+
+
+ โจ
+ Server Boost Perks
+
+
+ {boostFeatures.slice(0, serverData.boostLevel).map((tier) => (
+ tier.features.map((feature, idx) => (
+
+ โ
+ {feature}
+
+ ))
+ ))}
+
+
+ )}
+
+ {serverData.vanityUrl && (
+
+ Vanity URL
+ aethex.gg/{serverData.vanityUrl}
+
+ )}
+
+ );
+}
+
+// Progress bar for boost goals
+export function BoostProgress({ currentBoosts, currentLevel }) {
+ const levels = [
+ { level: 1, required: 2 },
+ { level: 2, required: 7 },
+ { level: 3, required: 14 },
+ ];
+
+ const nextLevel = levels.find((l) => l.level > currentLevel);
+ const prevLevel = levels.find((l) => l.level === currentLevel) || { required: 0 };
+
+ if (!nextLevel) {
+ return (
+
+
Maximum Level Reached!
+
+
{currentBoosts} Boosts
+
+ );
+ }
+
+ const progress = ((currentBoosts - prevLevel.required) / (nextLevel.required - prevLevel.required)) * 100;
+
+ return (
+
+
+ Level {currentLevel}
+ Level {nextLevel.level}
+
+
+
+ {currentBoosts} / {nextLevel.required} Boosts
+
+
+ );
+}
diff --git a/src/frontend/mockup/ServerDiscovery.jsx b/src/frontend/mockup/ServerDiscovery.jsx
new file mode 100644
index 0000000..be9277b
--- /dev/null
+++ b/src/frontend/mockup/ServerDiscovery.jsx
@@ -0,0 +1,199 @@
+import React, { useState } from 'react';
+
+const categories = [
+ { id: 'home', label: 'Home', icon: '๐ ' },
+ { id: 'gaming', label: 'Gaming', icon: '๐ฎ' },
+ { id: 'music', label: 'Music', icon: '๐ต' },
+ { id: 'entertainment', label: 'Entertainment', icon: '๐ฌ' },
+ { id: 'education', label: 'Education', icon: '๐' },
+ { id: 'science', label: 'Science & Tech', icon: '๐ฌ' },
+ { id: 'crypto', label: 'Crypto', icon: '๐ฐ' },
+];
+
+const featuredServers = [
+ {
+ id: 1,
+ name: 'AeThex Foundation',
+ description: 'Official home of AeThex. Security, authentication, and core infrastructure discussions.',
+ icon: 'F',
+ gradient: 'linear-gradient(135deg, #ff0000, #990000)',
+ members: '128.5K',
+ online: '24.2K',
+ verified: true,
+ partnered: true,
+ banner: 'linear-gradient(135deg, #1a0000, #ff0000)',
+ tags: ['Security', 'Development', 'Official'],
+ },
+ {
+ id: 2,
+ name: 'AeThex Labs',
+ description: 'Experimental features, beta testing, and bleeding-edge technology development.',
+ icon: 'L',
+ gradient: 'linear-gradient(135deg, #ffa500, #ff6b00)',
+ members: '45.2K',
+ online: '8.1K',
+ verified: true,
+ partnered: false,
+ banner: 'linear-gradient(135deg, #1a0f00, #ffa500)',
+ tags: ['Beta', 'Innovation', 'Research'],
+ },
+ {
+ id: 3,
+ name: 'GameForge Community',
+ description: 'The ultimate gaming community. LFG, tournaments, and game discussions.',
+ icon: '๐ฎ',
+ members: '892.1K',
+ online: '156.3K',
+ verified: true,
+ partnered: true,
+ banner: 'linear-gradient(135deg, #2d1b69, #5865f2)',
+ tags: ['Gaming', 'LFG', 'Tournaments'],
+ },
+ {
+ id: 4,
+ name: 'Lofi Beats',
+ description: '24/7 lofi hip hop radio - beats to relax/study to',
+ icon: '๐ง',
+ members: '1.2M',
+ online: '89.4K',
+ verified: true,
+ partnered: true,
+ banner: 'linear-gradient(135deg, #1a1a2e, #16213e)',
+ tags: ['Music', 'Chill', 'Study'],
+ },
+];
+
+const popularServers = [
+ { id: 5, name: 'Python', icon: '๐', members: '456K', tags: ['Programming'] },
+ { id: 6, name: 'Anime Hub', icon: '๐', members: '1.8M', tags: ['Anime'] },
+ { id: 7, name: 'Art & Design', icon: '๐จ', members: '234K', tags: ['Creative'] },
+ { id: 8, name: 'Crypto Trading', icon: '๐', members: '567K', tags: ['Crypto'] },
+ { id: 9, name: 'Movie Night', icon: '๐ฌ', members: '123K', tags: ['Movies'] },
+ { id: 10, name: 'Book Club', icon: '๐', members: '89K', tags: ['Books'] },
+];
+
+export default function ServerDiscovery({ onJoin, onClose }) {
+ const [search, setSearch] = useState('');
+ const [activeCategory, setActiveCategory] = useState('home');
+
+ return (
+
+
+
+
Discover
+
+
+
+ ๐
+ setSearch(e.target.value)}
+ />
+
+
+
+ {categories.map(cat => (
+ setActiveCategory(cat.id)}
+ >
+ {cat.icon}
+ {cat.label}
+
+ ))}
+
+
+
+
+
โ
+
+
+ Featured Communities
+
+ {featuredServers.map(server => (
+
+
+ {server.verified && โ }
+ {server.partnered && ๐ค }
+
+
+
+ {server.icon}
+
+
{server.name}
+
{server.description}
+
+ {server.tags.map(tag => (
+ {tag}
+ ))}
+
+
+
+
+ {server.online} Online
+
+
+ ๐ฅ {server.members} Members
+
+
+
+
onJoin?.(server)}
+ >
+ Join
+
+
+ ))}
+
+
+
+
+ Popular Right Now
+
+ {popularServers.map(server => (
+
+
{server.icon}
+
+
{server.name}
+ {server.members} members
+
+
onJoin?.(server)}
+ >
+ Join
+
+
+ ))}
+
+
+
+
+ Browse by Category
+
+ {categories.filter(c => c.id !== 'home').map(cat => (
+ setActiveCategory(cat.id)}
+ >
+ {cat.icon}
+ {cat.label}
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/src/frontend/mockup/ServerInsights.jsx b/src/frontend/mockup/ServerInsights.jsx
new file mode 100644
index 0000000..23e0194
--- /dev/null
+++ b/src/frontend/mockup/ServerInsights.jsx
@@ -0,0 +1,256 @@
+import React, { useState } from 'react';
+
+const mockData = {
+ overview: {
+ totalMembers: 12847,
+ membersChange: '+342',
+ activeMembers: 3241,
+ activeChange: '+12%',
+ messagesPerDay: 8432,
+ messagesChange: '-5%',
+ joinRate: 89,
+ retentionRate: 76,
+ },
+ growth: [
+ { date: 'Jan 29', joins: 45, leaves: 12 },
+ { date: 'Jan 30', joins: 52, leaves: 8 },
+ { date: 'Jan 31', joins: 38, leaves: 15 },
+ { date: 'Feb 1', joins: 67, leaves: 10 },
+ { date: 'Feb 2', joins: 89, leaves: 7 },
+ { date: 'Feb 3', joins: 72, leaves: 11 },
+ { date: 'Feb 4', joins: 85, leaves: 9 },
+ ],
+ topChannels: [
+ { name: 'general', messages: 2341, percentage: 28 },
+ { name: 'gaming', messages: 1892, percentage: 22 },
+ { name: 'off-topic', messages: 1456, percentage: 17 },
+ { name: 'development', messages: 1234, percentage: 15 },
+ { name: 'announcements', messages: 892, percentage: 11 },
+ ],
+ peakHours: [
+ { hour: '12AM', activity: 15 },
+ { hour: '4AM', activity: 8 },
+ { hour: '8AM', activity: 25 },
+ { hour: '12PM', activity: 65 },
+ { hour: '4PM', activity: 85 },
+ { hour: '8PM', activity: 100 },
+ ],
+ demographics: {
+ regions: [
+ { name: 'North America', percentage: 42 },
+ { name: 'Europe', percentage: 31 },
+ { name: 'Asia', percentage: 18 },
+ { name: 'Other', percentage: 9 },
+ ],
+ platforms: [
+ { name: 'Desktop', percentage: 58 },
+ { name: 'Mobile', percentage: 35 },
+ { name: 'Web', percentage: 7 },
+ ],
+ },
+ invites: [
+ { code: 'abc123', uses: 234, creator: 'Admin' },
+ { code: 'def456', uses: 156, creator: 'Mod' },
+ { code: 'ghi789', uses: 89, creator: 'Member' },
+ ],
+};
+
+export default function ServerInsights({ onClose }) {
+ const [activeTab, setActiveTab] = useState('overview');
+ const [timeRange, setTimeRange] = useState('7d');
+
+ const tabs = [
+ { id: 'overview', label: 'Overview', icon: '๐' },
+ { id: 'growth', label: 'Growth', icon: '๐' },
+ { id: 'engagement', label: 'Engagement', icon: '๐ฌ' },
+ { id: 'audience', label: 'Audience', icon: '๐ฅ' },
+ ];
+
+ return (
+
+
+
๐ Server Insights
+
+ setTimeRange(e.target.value)}
+ >
+ Last 24 hours
+ Last 7 days
+ Last 30 days
+ Last 90 days
+
+ โ
+
+
+
+
+ {tabs.map(tab => (
+ setActiveTab(tab.id)}
+ >
+ {tab.icon} {tab.label}
+
+ ))}
+
+
+
+ {activeTab === 'overview' && (
+
+
+
+
๐ฅ
+
+ {mockData.overview.totalMembers.toLocaleString()}
+ Total Members
+ {mockData.overview.membersChange}
+
+
+
+
๐ข
+
+ {mockData.overview.activeMembers.toLocaleString()}
+ Active Members
+ {mockData.overview.activeChange}
+
+
+
+
๐ฌ
+
+ {mockData.overview.messagesPerDay.toLocaleString()}
+ Messages/Day
+ {mockData.overview.messagesChange}
+
+
+
+
๐ฅ
+
+ {mockData.overview.joinRate}%
+ Join Rate
+
+
+
+
+
+
Member Growth
+
+ {mockData.growth.map((day, idx) => (
+
+ ))}
+
+
+
+ )}
+
+ {activeTab === 'growth' && (
+
+
+
+ +{mockData.growth.reduce((a, d) => a + d.joins, 0)}
+ New Members
+
+
+ -{mockData.growth.reduce((a, d) => a + d.leaves, 0)}
+ Members Left
+
+
+ {mockData.overview.retentionRate}%
+ Retention Rate
+
+
+
+
+
Top Invite Links
+ {mockData.invites.map((invite, idx) => (
+
+ discord.gg/{invite.code}
+ {invite.uses} uses
+ by {invite.creator}
+
+ ))}
+
+
+ )}
+
+ {activeTab === 'engagement' && (
+
+
+
Most Active Channels
+ {mockData.topChannels.map((channel, idx) => (
+
+
#{idx + 1}
+
#{channel.name}
+
+
{channel.messages.toLocaleString()}
+
+ ))}
+
+
+
+
Peak Activity Hours
+
+ {mockData.peakHours.map((hour, idx) => (
+
+ ))}
+
+
+
+ )}
+
+ {activeTab === 'audience' && (
+
+
+
Top Regions
+ {mockData.demographics.regions.map((region, idx) => (
+
+
{region.name}
+
+
{region.percentage}%
+
+ ))}
+
+
+
+
Platforms
+ {mockData.demographics.platforms.map((platform, idx) => (
+
+
{platform.name}
+
+
{platform.percentage}%
+
+ ))}
+
+
+ )}
+
+
+ );
+}
diff --git a/src/frontend/mockup/ServerList.jsx b/src/frontend/mockup/ServerList.jsx
index a7b5d59..93d6846 100644
--- a/src/frontend/mockup/ServerList.jsx
+++ b/src/frontend/mockup/ServerList.jsx
@@ -1,30 +1,79 @@
-import React from "react";
+import React, { useState } from 'react';
const servers = [
- { id: "foundation", label: "F", active: true, className: "foundation" },
- { id: "corporation", label: "C", active: false, className: "corporation" },
- { id: "labs", label: "L", active: false, className: "labs" },
- { id: "divider" },
- { id: "community1", label: "AG", active: false, className: "community" },
- { id: "community2", label: "RD", active: false, className: "community" },
- { id: "add", label: "+", active: false, className: "community" },
+ { id: 'home', name: 'Direct Messages', initial: '๐ ', type: 'home', notifications: 4 },
+ { divider: true },
+ { id: 'foundation', name: 'AeThex Foundation', initial: 'F', type: 'foundation', notifications: 0 },
+ { id: 'corporation', name: 'AeThex Corporation', initial: 'C', type: 'corporation', notifications: 12 },
+ { id: 'labs', name: 'AeThex Labs', initial: 'L', type: 'labs', notifications: 0, hasUpdate: true },
+ { divider: true },
+ { id: 'gaming', name: 'AeThex Gaming', initial: 'AG', type: 'community', notifications: 0 },
+ { id: 'dev', name: 'Dev Community', initial: 'DC', type: 'community', notifications: 3 },
+ { id: 'music', name: 'Music Lounge', initial: '๐ต', type: 'community', notifications: 0 },
+ { divider: true },
+ { id: 'add', name: 'Add a Server', initial: '+', type: 'add' },
+ { id: 'explore', name: 'Explore Servers', initial: '๐งญ', type: 'explore' },
];
-export default function ServerList() {
+export default function ServerList({ activeServer, setActiveServer, onOpenDMs, onDMsClick, selectedDMs, onOpenSettings }) {
+ const [hoveredServer, setHoveredServer] = useState(null);
+
return (
-
- {servers.map((srv, i) =>
- srv.id === "divider" ? (
-
- ) : (
+
+ {servers.map((server, idx) => {
+ if (server.divider) {
+ return
;
+ }
+
+ const isActive = activeServer === server.id || (server.id === 'home' && selectedDMs);
+ const isHovered = hoveredServer === server.id;
+ let className = 'server-icon';
+ if (server.type !== 'home' && server.type !== 'add' && server.type !== 'explore') {
+ className += ` ${server.type}`;
+ }
+ if (isActive) className += ' active';
+
+ return (
{
+ if (server.id === 'home') {
+ onOpenDMs?.();
+ onDMsClick?.();
+ }
+ setActiveServer?.(server.id);
+ }}
+ onMouseEnter={() => setHoveredServer(server.id)}
+ onMouseLeave={() => setHoveredServer(null)}
+ title={server.name}
+ style={server.type === 'home' ? { background: isActive ? '#5865f2' : '#36393f' } :
+ server.type === 'add' ? { background: '#36393f', color: '#3ba55d' } :
+ server.type === 'explore' ? { background: '#36393f', color: '#3ba55d' } : undefined}
>
- {srv.label}
+ {server.initial}
+
+ {/* Notification Badge */}
+ {server.notifications > 0 && (
+
+ {server.notifications > 99 ? '99+' : server.notifications}
+
+ )}
+
+ {/* Update dot */}
+ {server.hasUpdate && !server.notifications && (
+
+ )}
+
+ {/* Tooltip */}
+ {isHovered && (
+
+ {server.name}
+
+ )}
- )
- )}
+ );
+ })}
);
}
diff --git a/src/frontend/mockup/ServerSettingsModal.jsx b/src/frontend/mockup/ServerSettingsModal.jsx
new file mode 100644
index 0000000..dfd1a7b
--- /dev/null
+++ b/src/frontend/mockup/ServerSettingsModal.jsx
@@ -0,0 +1,159 @@
+import React, { useState } from 'react';
+
+export default function ServerSettingsModal({ server, onClose }) {
+ const [activeTab, setActiveTab] = useState('overview');
+
+ const serverData = server || {
+ name: 'AeThex Foundation',
+ icon: 'F',
+ gradient: 'linear-gradient(135deg, #ff0000, #990000)',
+ description: 'Official AeThex Foundation server. Security, authentication, and core infrastructure discussions.',
+ region: 'US East',
+ memberCount: 128,
+ boostLevel: 3,
+ boostCount: 14,
+ };
+
+ const tabs = [
+ { id: 'overview', label: 'Overview', icon: '๐' },
+ { id: 'roles', label: 'Roles', icon: '๐ญ' },
+ { id: 'emoji', label: 'Emoji', icon: '๐' },
+ { id: 'stickers', label: 'Stickers', icon: '๐จ' },
+ { id: 'moderation', label: 'Moderation', icon: '๐ก๏ธ' },
+ { id: 'audit-log', label: 'Audit Log', icon: '๐' },
+ { id: 'integrations', label: 'Integrations', icon: '๐' },
+ { id: 'members', label: 'Members', icon: '๐ฅ' },
+ { id: 'invites', label: 'Invites', icon: 'โ๏ธ' },
+ { id: 'bans', label: 'Bans', icon: '๐ซ' },
+ ];
+
+ return (
+
+
e.stopPropagation()}>
+
+
+
+ {serverData.icon}
+
+
{serverData.name}
+
+
+
+ {tabs.map(tab => (
+
setActiveTab(tab.id)}
+ >
+ {tab.icon}
+ {tab.label}
+
+ ))}
+
+
+
+
+ ๐๏ธ
+ Delete Server
+
+
+
+
+
+
+
{tabs.find(t => t.id === activeTab)?.label}
+ โ
+
+
+ {activeTab === 'overview' && (
+
+
+ Server Name
+
+
+
+
+
Server Icon
+
+
+ {serverData.icon}
+
+
Upload Image
+
+
+
+
+ Description
+
+
+
+
+ Server Region
+
+ US East
+ US West
+ Europe
+ Asia
+
+
+
+
+
+ ๐
+ Level {serverData.boostLevel}
+
+
{serverData.boostCount} Boosts
+
+
+ )}
+
+ {activeTab === 'roles' && (
+
+
+ + Create Role
+
+
+
+
+ Founder
+ 1 member
+
+
+
+ Foundation
+ 8 members
+
+
+
+ Corporation
+ 24 members
+
+
+
+ Labs
+ 12 members
+
+
+
+ @everyone
+ 128 members
+
+
+
+ )}
+
+ {activeTab !== 'overview' && activeTab !== 'roles' && (
+
+
๐ง
+
Settings for {tabs.find(t => t.id === activeTab)?.label} coming soon
+
+ )}
+
+
+
+ );
+}
diff --git a/src/frontend/mockup/ServerTemplate.jsx b/src/frontend/mockup/ServerTemplate.jsx
new file mode 100644
index 0000000..8290508
--- /dev/null
+++ b/src/frontend/mockup/ServerTemplate.jsx
@@ -0,0 +1,247 @@
+import React, { useState } from 'react';
+
+const builtInTemplates = [
+ { id: 'gaming', name: 'Gaming', icon: '๐ฎ', description: 'Perfect for gaming communities', channels: ['general', 'voice-chat', 'lfg', 'clips', 'memes'], roles: ['Admin', 'Moderator', 'Member'] },
+ { id: 'school', name: 'School Club', icon: '๐', description: 'For study groups and school clubs', channels: ['announcements', 'homework-help', 'resources', 'off-topic', 'voice-study'], roles: ['Teacher', 'TA', 'Student'] },
+ { id: 'creator', name: 'Content Creator', icon: '๐ฌ', description: 'For streamers and creators', channels: ['announcements', 'stream-chat', 'clips', 'fan-art', 'suggestions'], roles: ['Creator', 'Mod', 'Subscriber', 'Fan'] },
+ { id: 'friends', name: 'Friends', icon: '๐ฅ', description: 'Casual hangout space', channels: ['general', 'memes', 'music', 'gaming'], roles: ['Admin', 'Member'] },
+ { id: 'local', name: 'Local Community', icon: '๐๏ธ', description: 'For local groups and neighborhoods', channels: ['announcements', 'events', 'marketplace', 'discussion'], roles: ['Organizer', 'Member'] },
+];
+
+const myTemplates = [
+ { id: 'my-dev', name: 'Dev Team Setup', icon: '๐ป', description: 'My development team template', channels: ['general', 'backend', 'frontend', 'devops', 'standup'], roles: ['Lead', 'Senior', 'Junior', 'Intern'], createdAt: '2025-01-15' },
+];
+
+export default function ServerTemplate({ mode = 'apply', currentServer, onClose, onApply, onSave }) {
+ const [activeTab, setActiveTab] = useState('browse');
+ const [selectedTemplate, setSelectedTemplate] = useState(null);
+ const [templateName, setTemplateName] = useState('');
+ const [templateDesc, setTemplateDesc] = useState('');
+ const [includeChannels, setIncludeChannels] = useState(true);
+ const [includeRoles, setIncludeRoles] = useState(true);
+ const [includeSettings, setIncludeSettings] = useState(true);
+
+ const handleApplyTemplate = () => {
+ if (selectedTemplate) {
+ onApply?.(selectedTemplate);
+ }
+ };
+
+ const handleSaveTemplate = () => {
+ onSave?.({
+ name: templateName,
+ description: templateDesc,
+ includeChannels,
+ includeRoles,
+ includeSettings,
+ });
+ };
+
+ return (
+
+
e.stopPropagation()}>
+
+
๐ Server Templates
+ โ
+
+
+
+ setActiveTab('browse')}
+ >
+ Browse Templates
+
+ setActiveTab('my-templates')}
+ >
+ My Templates
+
+ setActiveTab('create')}
+ >
+ Create Template
+
+
+
+
+ {activeTab === 'browse' && (
+
+
+ Start your server with a template to get channels, roles, and settings pre-configured.
+
+
+ {builtInTemplates.map(template => (
+
setSelectedTemplate(template)}
+ >
+
{template.icon}
+
+
{template.name}
+
{template.description}
+
+
+
+ Channels:
+ {template.channels.length}
+
+
+ Roles:
+ {template.roles.length}
+
+
+
+ ))}
+
+
+ {selectedTemplate && (
+
+
{selectedTemplate.icon} {selectedTemplate.name}
+
+
Channels
+
+ {selectedTemplate.channels.map(ch => (
+ #{ch}
+ ))}
+
+
+
+
Roles
+
+ {selectedTemplate.roles.map(role => (
+ @{role}
+ ))}
+
+
+
+ )}
+
+ )}
+
+ {activeTab === 'my-templates' && (
+
+ {myTemplates.length === 0 ? (
+
+
๐
+
No saved templates
+
Create a template from your server to save it here
+
+ ) : (
+
+ {myTemplates.map(template => (
+
setSelectedTemplate(template)}
+ >
+
{template.icon}
+
+
{template.name}
+
{template.description}
+
Created {template.createdAt}
+
+
+ โ๏ธ
+ ๐๏ธ
+
+
+ ))}
+
+ )}
+
+ )}
+
+ {activeTab === 'create' && (
+
+
+ Create a template from your current server configuration.
+
+
+
+
+ Template Name
+ setTemplateName(e.target.value)}
+ placeholder="My Server Template"
+ />
+
+
+
+ Description
+
+
+
+
+
+ โ ๏ธ
+ Templates do not include messages, members, or server-specific content.
+
+
+
+ )}
+
+
+
+ Cancel
+ {activeTab === 'create' ? (
+
+ Create Template
+
+ ) : (
+
+ Use Template
+
+ )}
+
+
+
+ );
+}
diff --git a/src/frontend/mockup/SlowModeSettings.jsx b/src/frontend/mockup/SlowModeSettings.jsx
new file mode 100644
index 0000000..25086b8
--- /dev/null
+++ b/src/frontend/mockup/SlowModeSettings.jsx
@@ -0,0 +1,186 @@
+import React, { useState } from 'react';
+
+const slowModePresets = [
+ { value: 0, label: 'Off' },
+ { value: 5, label: '5s' },
+ { value: 10, label: '10s' },
+ { value: 15, label: '15s' },
+ { value: 30, label: '30s' },
+ { value: 60, label: '1m' },
+ { value: 120, label: '2m' },
+ { value: 300, label: '5m' },
+ { value: 600, label: '10m' },
+ { value: 900, label: '15m' },
+ { value: 1800, label: '30m' },
+ { value: 3600, label: '1h' },
+ { value: 7200, label: '2h' },
+ { value: 21600, label: '6h' },
+];
+
+export default function SlowModeSettings({ channel, onSave, onClose }) {
+ const [slowMode, setSlowMode] = useState(channel?.slowMode || 0);
+ const [customMode, setCustomMode] = useState(false);
+ const [customSeconds, setCustomSeconds] = useState('');
+ const [exemptRoles, setExemptRoles] = useState(channel?.exemptRoles || []);
+ const [showExemptSettings, setShowExemptSettings] = useState(false);
+
+ const roles = [
+ { id: 'admin', name: 'Admin', color: '#ff0000' },
+ { id: 'mod', name: 'Moderator', color: '#5865f2' },
+ { id: 'helper', name: 'Helper', color: '#3ba55d' },
+ { id: 'vip', name: 'VIP', color: '#faa61a' },
+ { id: 'booster', name: 'Server Booster', color: '#ff73fa' },
+ ];
+
+ const formatDuration = (seconds) => {
+ if (seconds === 0) return 'Off';
+ if (seconds < 60) return `${seconds} second${seconds !== 1 ? 's' : ''}`;
+ if (seconds < 3600) return `${Math.floor(seconds / 60)} minute${seconds >= 120 ? 's' : ''}`;
+ return `${Math.floor(seconds / 3600)} hour${seconds >= 7200 ? 's' : ''}`;
+ };
+
+ const handlePresetClick = (value) => {
+ setSlowMode(value);
+ setCustomMode(false);
+ };
+
+ const handleCustomApply = () => {
+ const value = parseInt(customSeconds);
+ if (!isNaN(value) && value >= 0 && value <= 21600) {
+ setSlowMode(value);
+ setCustomMode(false);
+ }
+ };
+
+ const toggleExemptRole = (roleId) => {
+ setExemptRoles(prev =>
+ prev.includes(roleId)
+ ? prev.filter(id => id !== roleId)
+ : [...prev, roleId]
+ );
+ };
+
+ const handleSave = () => {
+ onSave?.({
+ slowMode,
+ exemptRoles,
+ });
+ };
+
+ return (
+
+
e.stopPropagation()}>
+
+
โฑ๏ธ Slowmode Settings
+ โ
+
+
+
+
+ #
+ {channel?.name || 'general'}
+
+
+
+
Slowmode Duration
+
+ Members can only send one message per {formatDuration(slowMode)}.
+
+
+
+ {slowModePresets.map(preset => (
+ handlePresetClick(preset.value)}
+ >
+ {preset.label}
+
+ ))}
+ setCustomMode(true)}
+ >
+ Custom
+
+
+
+ {customMode && (
+
+ setCustomSeconds(e.target.value)}
+ placeholder="Seconds (0-21600)"
+ />
+
+ Apply
+
+
+ )}
+
+
+ setSlowMode(parseInt(e.target.value))}
+ />
+ {formatDuration(slowMode)}
+
+
+
+
+
setShowExemptSettings(!showExemptSettings)}
+ >
+
Exempt Roles
+ {showExemptSettings ? 'โผ' : 'โถ'}
+
+
+ {showExemptSettings && (
+
+
+ These roles won't be affected by slowmode.
+
+ {roles.map(role => (
+
+ toggleExemptRole(role.id)}
+ />
+
+ @{role.name}
+
+
+ ))}
+
+ )}
+
+
+
+ โน๏ธ
+
+ Slowmode helps prevent spam by limiting how often members can send messages.
+ Members with "Manage Channel" permission are always exempt.
+
+
+
+
+
+ Cancel
+ Save Changes
+
+
+
+ );
+}
diff --git a/src/frontend/mockup/Soundboard.jsx b/src/frontend/mockup/Soundboard.jsx
new file mode 100644
index 0000000..1f8b16d
--- /dev/null
+++ b/src/frontend/mockup/Soundboard.jsx
@@ -0,0 +1,129 @@
+import React, { useState } from 'react';
+
+const sounds = [
+ { id: 1, name: 'Airhorn', emoji: '๐ฏ', duration: '1.2s', category: 'effects' },
+ { id: 2, name: 'Cricket', emoji: '๐ฆ', duration: '2.0s', category: 'effects' },
+ { id: 3, name: 'Sad Trombone', emoji: '๐ฏ', duration: '1.8s', category: 'effects' },
+ { id: 4, name: 'Clap', emoji: '๐', duration: '0.5s', category: 'effects' },
+ { id: 5, name: 'Dun Dun Dun', emoji: '๐ต', duration: '1.5s', category: 'effects' },
+ { id: 6, name: 'Wow', emoji: '๐ฎ', duration: '0.8s', category: 'voice' },
+ { id: 7, name: 'Bruh', emoji: '๐', duration: '0.5s', category: 'voice' },
+ { id: 8, name: "Let's Go", emoji: '๐ฅ', duration: '0.7s', category: 'voice' },
+ { id: 9, name: 'GG', emoji: '๐ฎ', duration: '0.6s', category: 'voice' },
+ { id: 10, name: 'Oof', emoji: '๐', duration: '0.4s', category: 'voice' },
+ { id: 11, name: 'Windows Error', emoji: '๐ป', duration: '0.8s', category: 'memes' },
+ { id: 12, name: 'Vine Boom', emoji: '๐ฅ', duration: '0.6s', category: 'memes' },
+];
+
+export default function Soundboard({ onPlay, onClose }) {
+ const [search, setSearch] = useState('');
+ const [activeCategory, setActiveCategory] = useState('all');
+ const [volume, setVolume] = useState(50);
+ const [playing, setPlaying] = useState(null);
+ const [favorites, setFavorites] = useState([1, 8, 12]);
+
+ const categories = [
+ { id: 'all', label: 'All' },
+ { id: 'favorites', label: 'โญ Favorites' },
+ { id: 'effects', label: 'Effects' },
+ { id: 'voice', label: 'Voice' },
+ { id: 'memes', label: 'Memes' },
+ ];
+
+ const filteredSounds = sounds.filter(s => {
+ const matchesSearch = !search || s.name.toLowerCase().includes(search.toLowerCase());
+ const matchesCategory = activeCategory === 'all' ||
+ (activeCategory === 'favorites' ? favorites.includes(s.id) : s.category === activeCategory);
+ return matchesSearch && matchesCategory;
+ });
+
+ const handlePlay = (sound) => {
+ setPlaying(sound.id);
+ onPlay?.(sound);
+ // Simulate sound duration
+ setTimeout(() => setPlaying(null), parseFloat(sound.duration) * 1000);
+ };
+
+ const toggleFavorite = (e, soundId) => {
+ e.stopPropagation();
+ setFavorites(prev =>
+ prev.includes(soundId)
+ ? prev.filter(id => id !== soundId)
+ : [...prev, soundId]
+ );
+ };
+
+ return (
+
+
+
Soundboard
+ โ
+
+
+
+ ๐
+ setSearch(e.target.value)}
+ />
+
+
+
+ {categories.map(cat => (
+ setActiveCategory(cat.id)}
+ >
+ {cat.label}
+
+ ))}
+
+
+
+ {filteredSounds.map(sound => (
+
handlePlay(sound)}
+ >
+ {sound.emoji}
+ {sound.name}
+ {sound.duration}
+ toggleFavorite(e, sound.id)}
+ >
+ {favorites.includes(sound.id) ? 'โญ' : 'โ'}
+
+ {playing === sound.id &&
}
+
+ ))}
+
+
+ {filteredSounds.length === 0 && (
+
+ No sounds found
+
+ )}
+
+
+
+ ๐
+ setVolume(Number(e.target.value))}
+ className="volume-slider"
+ />
+ {volume}%
+
+
+ Upload Sound
+
+
+ );
+}
diff --git a/src/frontend/mockup/StageChannel.jsx b/src/frontend/mockup/StageChannel.jsx
new file mode 100644
index 0000000..b066eb4
--- /dev/null
+++ b/src/frontend/mockup/StageChannel.jsx
@@ -0,0 +1,152 @@
+import React, { useState } from 'react';
+
+export default function StageChannel({ channel, onClose, onLeave }) {
+ const [isLive, setIsLive] = useState(true);
+ const [isSpeaker, setIsSpeaker] = useState(false);
+ const [handRaised, setHandRaised] = useState(false);
+ const [muted, setMuted] = useState(true);
+
+ const stageData = channel || {
+ name: 'Community Townhall',
+ topic: 'Q&A with the AeThex Team - Monthly Update',
+ startedAt: '2 hours ago',
+ listeners: 234,
+ };
+
+ const speakers = [
+ { id: 1, name: 'Anderson', avatar: 'A', color: '#ff0000', speaking: true, role: 'Host' },
+ { id: 2, name: 'Trevor', avatar: 'T', color: '#0066ff', speaking: false, role: 'Speaker' },
+ { id: 3, name: 'Sarah', avatar: 'S', color: '#ffa500', speaking: false, role: 'Speaker' },
+ ];
+
+ const audience = [
+ { id: 4, name: 'Marcus', avatar: 'M', handRaised: true },
+ { id: 5, name: 'DevUser_123', avatar: 'D', handRaised: false },
+ { id: 6, name: 'Player_456', avatar: 'P', handRaised: true },
+ { id: 7, name: 'CoolGamer', avatar: 'C', handRaised: false },
+ { id: 8, name: 'TechFan', avatar: 'T', handRaised: false },
+ ];
+
+ const handleRequestSpeak = () => {
+ setHandRaised(!handRaised);
+ };
+
+ return (
+
+
+
+
+
+ LIVE
+
+
{stageData.name}
+
{stageData.topic}
+
+
โ
+
+
+
+
+
+ ๐๏ธ
+ Speakers โ {speakers.length}
+
+
+ {speakers.map(speaker => (
+
+
+ {speaker.avatar}
+ {speaker.speaking &&
}
+
+
{speaker.name}
+
{speaker.role}
+
+ ))}
+
+
+
+
+
+ ๐ฅ
+ Audience โ {audience.length}
+
+
+ {audience.map(user => (
+
+
{user.avatar}
+
{user.name}
+ {user.handRaised &&
โ }
+
+ ))}
+
+
+
+
+
+ ๐๏ธ {stageData.listeners} listening
+ โข
+ Started {stageData.startedAt}
+
+
+
+ {isSpeaker ? (
+ <>
+ setMuted(!muted)}
+ >
+ {muted ? '๐' : '๐ค'}
+ {muted ? 'Unmute' : 'Mute'}
+
+ setIsSpeaker(false)}>
+ ๐ฅ
+ Move to Audience
+
+ >
+ ) : (
+
+ โ
+ {handRaised ? 'Lower Hand' : 'Request to Speak'}
+
+ )}
+
+
+ ๐
+ Leave Stage
+
+
+
+ );
+}
+
+// Stage channel in sidebar
+export function StageChannelPreview({ channel, onJoin }) {
+ return (
+
+
+
๐ข
+
{channel.name}
+
+
+ {channel.topic && (
+
{channel.topic}
+ )}
+
+ ๐๏ธ {channel.speakers} speaking
+ ๐๏ธ {channel.listeners}
+
+
+ Join Stage
+
+
+ );
+}
diff --git a/src/frontend/mockup/StickerPicker.jsx b/src/frontend/mockup/StickerPicker.jsx
new file mode 100644
index 0000000..56aed39
--- /dev/null
+++ b/src/frontend/mockup/StickerPicker.jsx
@@ -0,0 +1,142 @@
+import React, { useState } from 'react';
+
+const stickerPacks = [
+ {
+ id: 'default',
+ name: 'Wumpus',
+ icon: '๐',
+ stickers: [
+ { id: 1, emoji: '๐', name: 'Wave' },
+ { id: 2, emoji: 'โค๏ธ', name: 'Love' },
+ { id: 3, emoji: '๐ญ', name: 'Cry' },
+ { id: 4, emoji: '๐', name: 'Party' },
+ { id: 5, emoji: '๐', name: 'Cool' },
+ { id: 6, emoji: '๐ค', name: 'Think' },
+ ]
+ },
+ {
+ id: 'aethex',
+ name: 'AeThex',
+ icon: '๐บ',
+ stickers: [
+ { id: 7, emoji: '๐ฅ', name: 'Fire' },
+ { id: 8, emoji: 'โก', name: 'Lightning' },
+ { id: 9, emoji: '๐', name: 'Launch' },
+ { id: 10, emoji: '๐', name: 'Gem' },
+ { id: 11, emoji: '๐ฎ', name: 'Crystal' },
+ { id: 12, emoji: 'โญ', name: 'Star' },
+ ]
+ },
+ {
+ id: 'gaming',
+ name: 'Gaming',
+ icon: '๐ฎ',
+ stickers: [
+ { id: 13, emoji: '๐', name: 'Trophy' },
+ { id: 14, emoji: '๐ช', name: 'Strong' },
+ { id: 15, emoji: '๐ฏ', name: 'Target' },
+ { id: 16, emoji: 'โ๏ธ', name: 'Swords' },
+ { id: 17, emoji: '๐ก๏ธ', name: 'Shield' },
+ { id: 18, emoji: '๐', name: 'Crown' },
+ ]
+ }
+];
+
+export default function StickerPicker({ onSelect, onClose }) {
+ const [search, setSearch] = useState('');
+ const [activePack, setActivePack] = useState('default');
+ const [recentStickers, setRecentStickers] = useState([1, 7, 13]);
+
+ const currentPack = stickerPacks.find(p => p.id === activePack);
+
+ const allStickers = stickerPacks.flatMap(p => p.stickers);
+ const filteredStickers = search
+ ? allStickers.filter(s => s.name.toLowerCase().includes(search.toLowerCase()))
+ : currentPack?.stickers || [];
+
+ const handleSelect = (sticker) => {
+ setRecentStickers(prev => [sticker.id, ...prev.filter(id => id !== sticker.id)].slice(0, 6));
+ onSelect?.(sticker);
+ };
+
+ return (
+
+
+
+ ๐
+ setSearch(e.target.value)}
+ />
+
+
+
+ {!search && recentStickers.length > 0 && (
+
+
+ Recently Used
+
+
+ {recentStickers.map(id => {
+ const sticker = allStickers.find(s => s.id === id);
+ if (!sticker) return null;
+ return (
+
handleSelect(sticker)}
+ title={sticker.name}
+ >
+ {sticker.emoji}
+
+ );
+ })}
+
+
+ )}
+
+
+ {stickerPacks.map(pack => (
+
setActivePack(pack.id)}
+ title={pack.name}
+ >
+ {pack.icon}
+
+ ))}
+
+
+
+
+
+
+ {!search && (
+
+ {currentPack?.name}
+
+ )}
+
+ {filteredStickers.map(sticker => (
+
handleSelect(sticker)}
+ title={sticker.name}
+ >
+
{sticker.emoji}
+
+ ))}
+
+
+
+ {search && filteredStickers.length === 0 && (
+
+ No stickers found
+
+ )}
+
+ );
+}
diff --git a/src/frontend/mockup/StreamOverlay.jsx b/src/frontend/mockup/StreamOverlay.jsx
new file mode 100644
index 0000000..f0883cb
--- /dev/null
+++ b/src/frontend/mockup/StreamOverlay.jsx
@@ -0,0 +1,238 @@
+import React, { useState } from 'react';
+
+export default function StreamOverlay({ onStart, onClose }) {
+ const [streamType, setStreamType] = useState('screen');
+ const [source, setSource] = useState(null);
+ const [quality, setQuality] = useState('720p');
+ const [fps, setFps] = useState('30');
+
+ const streamSources = {
+ screen: [
+ { id: 'screen-1', name: 'Screen 1', type: 'Screen', preview: '๐ฅ๏ธ' },
+ { id: 'screen-2', name: 'Screen 2', type: 'Screen', preview: '๐ฅ๏ธ' },
+ ],
+ window: [
+ { id: 'vscode', name: 'Visual Studio Code', type: 'Window', preview: '๐ป' },
+ { id: 'chrome', name: 'Google Chrome', type: 'Window', preview: '๐' },
+ { id: 'terminal', name: 'Terminal', type: 'Window', preview: 'โจ๏ธ' },
+ { id: 'game', name: 'Game.exe', type: 'Game', preview: '๐ฎ' },
+ ],
+ };
+
+ const qualities = [
+ { value: '480p', label: '480p' },
+ { value: '720p', label: '720p' },
+ { value: '1080p', label: '1080p', nitro: true },
+ { value: '1440p', label: '1440p', nitro: true },
+ { value: '4k', label: '4K (Source)', nitro: true },
+ ];
+
+ const framerates = [
+ { value: '15', label: '15 FPS' },
+ { value: '30', label: '30 FPS' },
+ { value: '60', label: '60 FPS', nitro: true },
+ ];
+
+ const handleStart = () => {
+ if (!source) return;
+ onStart?.({
+ type: streamType,
+ source,
+ quality,
+ fps,
+ });
+ };
+
+ return (
+
+
e.stopPropagation()}>
+
+
๐บ Go Live
+ โ
+
+
+
+
+ { setStreamType('screen'); setSource(null); }}
+ >
+ ๐ฅ๏ธ Screen
+
+ { setStreamType('window'); setSource(null); }}
+ >
+ ๐ช Window
+
+
+
+
+ {streamSources[streamType]?.map(src => (
+
setSource(src)}
+ >
+ {src.preview}
+
+ {src.name}
+ {src.type}
+
+ {source?.id === src.id && (
+ โ
+ )}
+
+ ))}
+
+
+
+
Stream Quality
+
+
+
Resolution
+
+ {qualities.map(q => (
+ setQuality(q.value)}
+ disabled={q.nitro}
+ >
+ {q.label}
+ {q.nitro && ๐ }
+
+ ))}
+
+
+
+
+
Frame Rate
+
+ {framerates.map(f => (
+ setFps(f.value)}
+ disabled={f.nitro}
+ >
+ {f.label}
+ {f.nitro && ๐ }
+
+ ))}
+
+
+
+
+
๐
+
+
Stream in higher quality with Nitro
+
Learn more
+
+
+
+
+
+
Preview
+
+ {source ? (
+
+ {source.preview}
+ {source.name}
+
+ ) : (
+
+ Select a source to preview
+
+ )}
+
+
+
+
+
+
+ {source && (
+ Streaming {source.name} at {quality} {fps}fps
+ )}
+
+
+ Cancel
+
+ Go Live
+
+
+
+
+
+ );
+}
+
+// Stream viewer component (when watching someone's stream)
+export function StreamViewer({ stream, onClose }) {
+ const [isFullscreen, setIsFullscreen] = useState(false);
+ const [volume, setVolume] = useState(100);
+
+ return (
+
+
+
+ ๐บ
+ {stream?.source?.name || 'Stream'}
+
+
+
+
+
+
+
+ {stream?.user?.avatar || 'S'}
+
+
+ {stream?.user?.name || 'Streamer'}
+ ๐๏ธ {stream?.viewers || 0} viewers
+
+
+
+
+
+ setVolume(v => v > 0 ? 0 : 100)}>
+ {volume === 0 ? '๐' : '๐'}
+
+ setVolume(Number(e.target.value))}
+ className="volume-slider"
+ />
+
+
setIsFullscreen(!isFullscreen)}>
+ {isFullscreen ? 'โถ' : 'โถ'}
+
+
+ โ
+
+
+
+
+
+ );
+}
+
+// Stream badge for voice channel
+export function StreamBadge({ stream }) {
+ return (
+
+ ๐บ
+ LIVE
+
+ );
+}
diff --git a/src/frontend/mockup/ThreadPanel.jsx b/src/frontend/mockup/ThreadPanel.jsx
new file mode 100644
index 0000000..0ccca52
--- /dev/null
+++ b/src/frontend/mockup/ThreadPanel.jsx
@@ -0,0 +1,114 @@
+import React, { useState } from 'react';
+
+export default function ThreadPanel({ thread, onClose }) {
+ const [replyText, setReplyText] = useState('');
+
+ const threadData = thread || {
+ name: 'Authentication Discussion',
+ parentMessage: {
+ author: 'Trevor',
+ avatar: 'T',
+ gradient: 'linear-gradient(135deg, #ff0000, #cc0000)',
+ content: 'Just pushed the authentication updates. All services should automatically migrate to the new protocols within 24 hours.',
+ timestamp: '10:34 AM',
+ },
+ replies: [
+ {
+ id: 1,
+ author: 'Marcus',
+ avatar: 'M',
+ gradient: 'linear-gradient(135deg, #0066ff, #003380)',
+ content: 'Great work! How does this affect the API rate limiting?',
+ timestamp: '10:36 AM',
+ },
+ {
+ id: 2,
+ author: 'Trevor',
+ avatar: 'T',
+ gradient: 'linear-gradient(135deg, #ff0000, #cc0000)',
+ content: 'Rate limiting remains the same. The auth tokens now include refresh capabilities though.',
+ timestamp: '10:38 AM',
+ },
+ {
+ id: 3,
+ author: 'Sarah',
+ avatar: 'S',
+ gradient: 'linear-gradient(135deg, #ffa500, #ff8c00)',
+ content: 'Testing this on Labs now. Will report back in a few hours.',
+ timestamp: '10:45 AM',
+ },
+ ],
+ memberCount: 4,
+ messageCount: 3,
+ };
+
+ const handleSendReply = () => {
+ if (!replyText.trim()) return;
+ // Handle sending reply
+ setReplyText('');
+ };
+
+ return (
+
+
+
+ ๐งต
+ {threadData.name}
+
+
+ {threadData.memberCount} members
+ โข
+ {threadData.messageCount} messages
+
+
โ
+
+
+
+
+
+ {threadData.parentMessage.avatar}
+
+
+
+ {threadData.parentMessage.author}
+ {threadData.parentMessage.timestamp}
+
+
{threadData.parentMessage.content}
+
+
+
+
+
+
+ {threadData.replies.map(reply => (
+
+
+ {reply.avatar}
+
+
+
+ {reply.author}
+ {reply.timestamp}
+
+
{reply.content}
+
+
+ ))}
+
+
+
+ setReplyText(e.target.value)}
+ onKeyPress={(e) => e.key === 'Enter' && handleSendReply()}
+ />
+
+ โค
+
+
+
+ );
+}
diff --git a/src/frontend/mockup/TypingIndicator.jsx b/src/frontend/mockup/TypingIndicator.jsx
new file mode 100644
index 0000000..1259db3
--- /dev/null
+++ b/src/frontend/mockup/TypingIndicator.jsx
@@ -0,0 +1,28 @@
+import React from 'react';
+
+export default function TypingIndicator({ users }) {
+ if (!users || users.length === 0) return null;
+
+ const getTypingText = () => {
+ if (users.length === 1) {
+ return <>
{users[0].name} is typing>;
+ } else if (users.length === 2) {
+ return <>
{users[0].name} and
{users[1].name} are typing>;
+ } else if (users.length === 3) {
+ return <>
{users[0].name} ,
{users[1].name} , and
{users[2].name} are typing>;
+ } else {
+ return <>
Several people are typing>;
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
{getTypingText()}...
+
+ );
+}
diff --git a/src/frontend/mockup/UserProfileCard.jsx b/src/frontend/mockup/UserProfileCard.jsx
new file mode 100644
index 0000000..627d035
--- /dev/null
+++ b/src/frontend/mockup/UserProfileCard.jsx
@@ -0,0 +1,165 @@
+import React from 'react';
+
+export default function UserProfileCard({ user, onClose, onMessage, onCall, onAddFriend }) {
+ const defaultUser = {
+ id: 1,
+ name: 'Anderson',
+ tag: 'Anderson#0001',
+ initial: 'A',
+ gradient: 'linear-gradient(135deg, #ff0000, #0066ff, #ffa500)',
+ banner: 'linear-gradient(135deg, #ff0000 0%, #0066ff 50%, #ffa500 100%)',
+ status: 'online',
+ customStatus: 'Building AeThex',
+ about: 'Founder of AeThex. Building the future of metaverse communication and decentralized identity.',
+ memberSince: 'Jan 2024',
+ roles: [
+ { name: 'Founder', color: '#ff0000' },
+ { name: 'Foundation', color: '#ff0000' },
+ ],
+ activity: {
+ type: 'developing',
+ name: 'AeThex Connect',
+ details: 'Working on messaging system',
+ elapsed: '2h 34m',
+ },
+ };
+
+ // Merge user prop with defaults
+ const userData = { ...defaultUser, ...user };
+
+ const statusColors = {
+ online: '#3ba55d',
+ idle: '#faa61a',
+ dnd: '#ed4245',
+ offline: '#747f8d',
+ 'in-game': '#5865f2',
+ labs: '#ffa500',
+ };
+
+ const handleOverlayClick = (e) => {
+ if (e.target === e.currentTarget) {
+ onClose?.();
+ }
+ };
+
+ return (
+
+
+
+ โ
+
+
+
+
+
+
+
+ {userData.initial || userData.name?.charAt(0) || '?'}
+
+
+
+
+ {(userData.roles || []).slice(0, 3).map((role, idx) => (
+
+ {role.name.charAt(0)}
+
+ ))}
+
+
+
+
+
{userData.name}
+
{userData.tag || userData.name}
+
+ {userData.customStatus && (
+
+ ๐ป
+ {userData.customStatus}
+
+ )}
+
+
+
+
+ {userData.activity && (
+
+
ACTIVITY
+
+
๐จ
+
+
{userData.activity.name || userData.activity}
+
{userData.activity.details || ''}
+
{userData.activity.elapsed || ''}
+
+
+
+ )}
+
+ {userData.about && (
+
+
ABOUT ME
+
{userData.about}
+
+ )}
+
+
+
MEMBER SINCE
+
{userData.memberSince || 'Jan 2024'}
+
+
+ {userData.roles && userData.roles.length > 0 && (
+
+
ROLES โ {userData.roles.length}
+
+ {userData.roles.map((role, idx) => (
+
+
+ {role.name}
+
+ ))}
+
+
+ )}
+
+
+
NOTE
+
+
+
+
+ onMessage?.()}>
+ ๐ฌ Message
+
+ onCall?.()}>
+ ๐ Call
+
+ onAddFriend?.()}>
+ ๐ Add Friend
+
+
+
+
+ );
+}
diff --git a/src/frontend/mockup/UserSettingsBar.jsx b/src/frontend/mockup/UserSettingsBar.jsx
new file mode 100644
index 0000000..3a16610
--- /dev/null
+++ b/src/frontend/mockup/UserSettingsBar.jsx
@@ -0,0 +1,105 @@
+import React, { useState } from 'react';
+
+export default function UserSettingsBar({ user, onSettingsClick }) {
+ const [isMuted, setIsMuted] = useState(false);
+ const [isDeafened, setIsDeafened] = useState(false);
+ const [showStatusMenu, setShowStatusMenu] = useState(false);
+ const [status, setStatus] = useState('online');
+
+ const currentUser = user || {
+ name: 'Anderson',
+ tag: '#0001',
+ avatar: 'A',
+ gradient: 'linear-gradient(135deg, #ff0000, #0066ff, #ffa500)',
+ };
+
+ const statuses = [
+ { id: 'online', label: 'Online', color: '#3ba55d' },
+ { id: 'idle', label: 'Idle', color: '#faa61a' },
+ { id: 'dnd', label: 'Do Not Disturb', color: '#ed4245' },
+ { id: 'invisible', label: 'Invisible', color: '#747f8d' },
+ ];
+
+ const toggleMute = () => {
+ setIsMuted(!isMuted);
+ if (isDeafened && !isMuted) setIsDeafened(false);
+ };
+
+ const toggleDeafen = () => {
+ setIsDeafened(!isDeafened);
+ if (!isDeafened) setIsMuted(true);
+ };
+
+ return (
+
+
setShowStatusMenu(!showStatusMenu)}>
+
+
+ {currentUser.avatar}
+
+
s.id === status)?.color }}
+ >
+
+
+
{currentUser.name}
+
{currentUser.tag}
+
+
+ {showStatusMenu && (
+
+ {statuses.map(s => (
+
{
+ e.stopPropagation();
+ setStatus(s.id);
+ setShowStatusMenu(false);
+ }}
+ >
+
+
{s.label}
+
+ ))}
+
+
+ ๐
+ Set Custom Status
+
+
+ )}
+
+
+
+
+ {isMuted ? '๐' : '๐ค'}
+ {isMuted &&
}
+
+
+ {isDeafened ? '๐' : '๐ง'}
+ {isDeafened &&
}
+
+
+ โ๏ธ
+
+
+
+ );
+}
diff --git a/src/frontend/mockup/UserSettingsModal.jsx b/src/frontend/mockup/UserSettingsModal.jsx
new file mode 100644
index 0000000..0f7fbaa
--- /dev/null
+++ b/src/frontend/mockup/UserSettingsModal.jsx
@@ -0,0 +1,375 @@
+import React, { useState } from 'react';
+
+export default function UserSettingsModal({ onClose }) {
+ const [activeTab, setActiveTab] = useState('my-account');
+
+ const settingsSections = [
+ {
+ title: 'USER SETTINGS',
+ items: [
+ { id: 'my-account', label: 'My Account', icon: '๐ค' },
+ { id: 'profiles', label: 'Profiles', icon: '๐จ' },
+ { id: 'privacy', label: 'Privacy & Safety', icon: '๐' },
+ { id: 'family', label: 'Family Center', icon: '๐จโ๐ฉโ๐ง' },
+ { id: 'authorized-apps', label: 'Authorized Apps', icon: '๐' },
+ { id: 'devices', label: 'Devices', icon: '๐ฑ' },
+ { id: 'connections', label: 'Connections', icon: '๐' },
+ { id: 'clips', label: 'Clips', icon: '๐ฌ' },
+ { id: 'friend-requests', label: 'Friend Requests', icon: '๐' },
+ ],
+ },
+ {
+ title: 'BILLING SETTINGS',
+ items: [
+ { id: 'nitro', label: 'Nitro', icon: '๐' },
+ { id: 'server-boost', label: 'Server Boost', icon: '๐' },
+ { id: 'subscriptions', label: 'Subscriptions', icon: '๐' },
+ { id: 'gift-inventory', label: 'Gift Inventory', icon: '๐' },
+ { id: 'billing', label: 'Billing', icon: '๐ณ' },
+ ],
+ },
+ {
+ title: 'APP SETTINGS',
+ items: [
+ { id: 'appearance', label: 'Appearance', icon: '๐จ' },
+ { id: 'accessibility', label: 'Accessibility', icon: 'โฟ' },
+ { id: 'voice-video', label: 'Voice & Video', icon: '๐๏ธ' },
+ { id: 'text-images', label: 'Text & Images', icon: '๐' },
+ { id: 'notifications', label: 'Notifications', icon: '๐' },
+ { id: 'keybinds', label: 'Keybinds', icon: 'โจ๏ธ' },
+ { id: 'language', label: 'Language', icon: '๐' },
+ { id: 'streamer-mode', label: 'Streamer Mode', icon: '๐บ' },
+ { id: 'advanced', label: 'Advanced', icon: 'โ๏ธ' },
+ ],
+ },
+ {
+ title: 'ACTIVITY SETTINGS',
+ items: [
+ { id: 'activity-privacy', label: 'Activity Privacy', icon: '๐๏ธ' },
+ { id: 'registered-games', label: 'Registered Games', icon: '๐ฎ' },
+ ],
+ },
+ ];
+
+ const handleOverlayClick = (e) => {
+ if (e.target === e.currentTarget) {
+ onClose?.();
+ }
+ };
+
+ return (
+
+
+
+
+ {settingsSections.map((section) => (
+
+
{section.title}
+ {section.items.map((item) => (
+
setActiveTab(item.id)}
+ >
+ {item.icon}
+ {item.label}
+
+ ))}
+
+
+ ))}
+
+
+ ๐ช
+ Log Out
+
+
+
+
+
+
+
{settingsSections.flatMap((s) => s.items).find((i) => i.id === activeTab)?.label}
+ โ
+
+
+ {activeTab === 'my-account' && (
+
+
+
+
+
A
+
+
Anderson
+ Anderson#0001
+
+
Edit User Profile
+
+
+
+
+
+
DISPLAY NAME
+
+ Anderson
+ Edit
+
+
+
+
USERNAME
+
+ anderson
+ Edit
+
+
+
+
EMAIL
+
+ a*****@aethex.dev
+ Edit
+
+
+
+
PHONE NUMBER
+
+ *******4567
+ Edit
+
+
+
+
+
+
+
+
Password and Authentication
+
Change Password
+
+
+
Two-Factor Authentication
+
Protect your AeThex account with an extra layer of security.
+
+
Enable 2FA
+
+
+
+
+
+
+
Account Removal
+
Disabling your account means you can recover it at any time after taking this action.
+
+ Disable Account
+ Delete Account
+
+
+
+ )}
+
+ {activeTab === 'appearance' && (
+
+
+
+
+
Message Display
+
+
+
+
+
Cozy
+
Discord text as it was meant to be.
+
+
+
+
+
+
Compact
+
Fit more messages on screen at once.
+
+
+
+
+
+
+ Chat Font Scaling โ 16px
+
+
+
+
+ Space Between Message Groups โ 16px
+
+
+
+
+ Zoom Level โ 100%
+
+
+
+ )}
+
+ {activeTab === 'voice-video' && (
+
+ )}
+
+ {activeTab === 'notifications' && (
+
+ )}
+
+ {activeTab === 'keybinds' && (
+
+
+ Add a Keybind
+
+
+
+
+
Push to Talk
+
` (backtick)
+
โ๏ธ
+
๐๏ธ
+
+
+
Toggle Mute
+
Ctrl + Shift + M
+
โ๏ธ
+
๐๏ธ
+
+
+
Toggle Deafen
+
Ctrl + Shift + D
+
โ๏ธ
+
๐๏ธ
+
+
+
+ )}
+
+ {!['my-account', 'appearance', 'voice-video', 'notifications', 'keybinds'].includes(activeTab) && (
+
+
๐ง
+
{settingsSections.flatMap((s) => s.items).find((i) => i.id === activeTab)?.label} settings coming soon
+
+ )}
+
+
+
+ );
+}
diff --git a/src/frontend/mockup/VideoCall.jsx b/src/frontend/mockup/VideoCall.jsx
new file mode 100644
index 0000000..a2b749c
--- /dev/null
+++ b/src/frontend/mockup/VideoCall.jsx
@@ -0,0 +1,206 @@
+import React, { useState } from 'react';
+
+export default function VideoCall({ users, onLeave, onToggleMute, onToggleDeafen, onToggleVideo, onToggleScreenShare }) {
+ const [isMuted, setIsMuted] = useState(false);
+ const [isDeafened, setIsDeafened] = useState(false);
+ const [isVideoOn, setIsVideoOn] = useState(true);
+ const [isScreenSharing, setIsScreenSharing] = useState(false);
+ const [isFullscreen, setIsFullscreen] = useState(false);
+ const [focusedUser, setFocusedUser] = useState(null);
+
+ const participants = users || [
+ { id: 1, name: 'Anderson', avatar: 'A', gradient: 'linear-gradient(135deg, #ff0000, #0066ff, #ffa500)', speaking: true, muted: false, video: true },
+ { id: 2, name: 'Trevor', avatar: 'T', gradient: 'linear-gradient(135deg, #ff0000, #cc0000)', speaking: false, muted: true, video: true },
+ { id: 3, name: 'Sarah', avatar: 'S', gradient: 'linear-gradient(135deg, #ffa500, #ff8c00)', speaking: false, muted: false, video: false, streaming: true },
+ { id: 4, name: 'Marcus', avatar: 'M', gradient: 'linear-gradient(135deg, #0066ff, #003380)', speaking: false, muted: false, video: false },
+ ];
+
+ const handleMute = () => {
+ setIsMuted(!isMuted);
+ onToggleMute?.(!isMuted);
+ };
+
+ const handleDeafen = () => {
+ setIsDeafened(!isDeafened);
+ onToggleDeafen?.(!isDeafened);
+ };
+
+ const handleVideo = () => {
+ setIsVideoOn(!isVideoOn);
+ onToggleVideo?.(!isVideoOn);
+ };
+
+ const handleScreenShare = () => {
+ setIsScreenSharing(!isScreenSharing);
+ onToggleScreenShare?.(!isScreenSharing);
+ };
+
+ const focusedParticipant = focusedUser ? participants.find((p) => p.id === focusedUser) : participants.find((p) => p.streaming) || participants[0];
+ const gridParticipants = participants.filter((p) => p.id !== focusedParticipant?.id);
+
+ return (
+
+ {/* Main/Focused Video Area */}
+
+
+ {focusedParticipant?.streaming ? (
+
+
+
+ ๐ฅ๏ธ
+ {focusedParticipant.name}'s screen
+
+
+
+
+ {focusedParticipant.avatar}
+
+
{focusedParticipant.name}
+
+
+ ) : focusedParticipant?.video ? (
+
+
+
+ {focusedParticipant.avatar}
+
+
+
{focusedParticipant.name}
+
+ ) : (
+
+
+ {focusedParticipant?.avatar}
+
+
{focusedParticipant?.name}
+ {focusedParticipant?.muted &&
๐
}
+
+ )}
+
+
+ {/* Participant Grid */}
+
+ {gridParticipants.map((participant) => (
+
setFocusedUser(participant.id)}
+ >
+ {participant.video ? (
+
+
+ {participant.avatar}
+
+
+ ) : (
+
+
+ {participant.avatar}
+
+
+ )}
+
+
{participant.name}
+
+ {participant.muted && ๐ }
+ {participant.streaming && ๐ฅ๏ธ }
+
+
+
+ ))}
+
+
+
+ {/* Controls Bar */}
+
+
+
+ ๐ Nexus Lounge
+ 1:23:45
+
+
+
+
+
+ {isMuted ? '๐' : '๐๏ธ'}
+
+
+ {isDeafened ? '๐' : '๐'}
+
+
+ {isVideoOn ? '๐น' : '๐ท'}
+
+
+ ๐ฅ๏ธ
+
+
+ ๐ฎ
+
+
+ ๐
+
+
+
+
+
+ ๐ฅ {participants.length}
+
+
+ ๐ฌ
+
+ setIsFullscreen(!isFullscreen)}
+ title={isFullscreen ? 'Exit Fullscreen' : 'Fullscreen'}
+ >
+ {isFullscreen ? 'โถ' : 'โถ'}
+
+
+
+
+ );
+}
diff --git a/src/frontend/mockup/VoiceChannel.jsx b/src/frontend/mockup/VoiceChannel.jsx
new file mode 100644
index 0000000..156b386
--- /dev/null
+++ b/src/frontend/mockup/VoiceChannel.jsx
@@ -0,0 +1,73 @@
+import React, { useState } from 'react';
+
+export default function VoiceChannel({ channel, onJoin, onLeave }) {
+ const [isExpanded, setIsExpanded] = useState(true);
+ const [isConnected, setIsConnected] = useState(false);
+
+ const voiceChannel = channel || {
+ name: 'Nexus Lounge',
+ users: [
+ { id: 1, name: 'Anderson', avatar: 'A', gradient: 'linear-gradient(135deg, #ff0000, #0066ff, #ffa500)', speaking: true, muted: false, deafened: false },
+ { id: 2, name: 'Sarah', avatar: 'S', gradient: 'linear-gradient(135deg, #ffa500, #ff8c00)', speaking: false, muted: true, deafened: false },
+ { id: 3, name: 'Marcus', avatar: 'M', gradient: 'linear-gradient(135deg, #0066ff, #003380)', speaking: false, muted: false, deafened: false, streaming: true },
+ ],
+ limit: 10,
+ bitrate: 64,
+ };
+
+ const handleConnect = () => {
+ setIsConnected(!isConnected);
+ if (!isConnected && onJoin) onJoin(voiceChannel);
+ if (isConnected && onLeave) onLeave();
+ };
+
+ return (
+
+
setIsExpanded(!isExpanded)}>
+ ๐
+ {voiceChannel.name}
+ {voiceChannel.users.length}/{voiceChannel.limit}
+ {isExpanded ? 'โผ' : 'โถ'}
+
+
+ {isExpanded && (
+
+ {voiceChannel.users.map(user => (
+
+
+ {user.avatar}
+ {user.speaking &&
}
+
+
{user.name}
+
+ {user.streaming && ๐บ }
+ {user.muted && ๐ }
+ {user.deafened && ๐ }
+
+
+ ))}
+
+ )}
+
+ {isConnected && (
+
+
+ Voice Connected
+ 23ms
+
+
+
+ ๐
+
+
+ ๐น
+
+
+ ๐ฅ๏ธ
+
+
+
+ )}
+
+ );
+}
diff --git a/src/frontend/mockup/WelcomeScreen.jsx b/src/frontend/mockup/WelcomeScreen.jsx
new file mode 100644
index 0000000..0883a32
--- /dev/null
+++ b/src/frontend/mockup/WelcomeScreen.jsx
@@ -0,0 +1,193 @@
+import React, { useState } from 'react';
+
+export default function WelcomeScreen({ server, onComplete, onClose }) {
+ const [step, setStep] = useState(0);
+ const [selectedRoles, setSelectedRoles] = useState([]);
+ const [selectedChannels, setSelectedChannels] = useState([]);
+
+ const serverData = server || {
+ name: 'AeThex Foundation',
+ icon: 'F',
+ gradient: 'linear-gradient(135deg, #ff0000, #990000)',
+ description: 'Official home of AeThex. Security, authentication, and core infrastructure.',
+ memberCount: '128.5K',
+ rules: [
+ { id: 1, title: 'Be Respectful', description: 'Treat everyone with respect. No harassment, bullying, or hate speech.' },
+ { id: 2, title: 'No Spam', description: 'Avoid excessive messages, self-promotion, or unsolicited advertisements.' },
+ { id: 3, title: 'Stay On Topic', description: 'Keep discussions relevant to the channel topic.' },
+ { id: 4, title: 'No NSFW Content', description: 'This is a SFW community. Keep content appropriate.' },
+ { id: 5, title: 'Follow Discord TOS', description: 'Adhere to Discord\'s Terms of Service and Community Guidelines.' },
+ ],
+ };
+
+ const roleOptions = [
+ { id: 'developer', label: '๐ป Developer', description: 'I build things with AeThex tools' },
+ { id: 'gamer', label: '๐ฎ Gamer', description: 'I\'m here for gaming content' },
+ { id: 'security', label: '๐ Security', description: 'Interested in security topics' },
+ { id: 'creator', label: '๐จ Creator', description: 'I create content' },
+ { id: 'announcements', label: '๐ข Announcements Only', description: 'Just here for updates' },
+ ];
+
+ const channelOptions = [
+ { id: 'general', label: '# general', description: 'Main community chat' },
+ { id: 'development', label: '# development', description: 'Dev discussions' },
+ { id: 'gaming', label: '# gaming', description: 'Gaming chat' },
+ { id: 'off-topic', label: '# off-topic', description: 'Random discussions' },
+ { id: 'showcase', label: '# showcase', description: 'Share your projects' },
+ ];
+
+ const steps = [
+ { id: 'welcome', title: 'Welcome' },
+ { id: 'rules', title: 'Rules' },
+ { id: 'roles', title: 'Pick Roles' },
+ { id: 'channels', title: 'Channels' },
+ ];
+
+ const handleComplete = () => {
+ onComplete?.({
+ roles: selectedRoles,
+ channels: selectedChannels,
+ });
+ };
+
+ const toggleRole = (roleId) => {
+ setSelectedRoles(prev =>
+ prev.includes(roleId)
+ ? prev.filter(id => id !== roleId)
+ : [...prev, roleId]
+ );
+ };
+
+ const toggleChannel = (channelId) => {
+ setSelectedChannels(prev =>
+ prev.includes(channelId)
+ ? prev.filter(id => id !== channelId)
+ : [...prev, channelId]
+ );
+ };
+
+ return (
+
+
+ {/* Progress dots */}
+
+ {steps.map((s, idx) => (
+
+ ))}
+
+
+ {step === 0 && (
+
+
+ {serverData.icon}
+
+
Welcome to {serverData.name}!
+
{serverData.description}
+
+ ๐ฅ {serverData.memberCount} members
+
+
setStep(1)}>
+ Continue
+
+
+ )}
+
+ {step === 1 && (
+
+
๐ Server Rules
+
Please read and agree to our community rules
+
+ {serverData.rules.map((rule, idx) => (
+
+
{idx + 1}
+
+
{rule.title}
+
{rule.description}
+
+
+ ))}
+
+
+ setStep(0)}>
+ Back
+
+ setStep(2)}>
+ I Agree
+
+
+
+ )}
+
+ {step === 2 && (
+
+
๐ญ Pick Your Roles
+
Select roles that match your interests
+
+ {roleOptions.map(role => (
+ toggleRole(role.id)}
+ >
+ {role.label}
+ {role.description}
+ {selectedRoles.includes(role.id) && (
+ โ
+ )}
+
+ ))}
+
+
+ setStep(1)}>
+ Back
+
+ setStep(3)}>
+ Continue
+
+
+
+ )}
+
+ {step === 3 && (
+
+
๐บ Choose Channels
+
Pick channels you want to see
+
+ {channelOptions.map(channel => (
+ toggleChannel(channel.id)}
+ >
+ {channel.label}
+ {channel.description}
+ {selectedChannels.includes(channel.id) && (
+ โ
+ )}
+
+ ))}
+
+
+ setStep(2)}>
+ Back
+
+
+ Finish Setup
+
+
+
+ )}
+
+
+ Skip for now
+
+
+
+ );
+}
diff --git a/src/frontend/mockup/contexts/AuthContext.jsx b/src/frontend/mockup/contexts/AuthContext.jsx
new file mode 100644
index 0000000..4a3a5f5
--- /dev/null
+++ b/src/frontend/mockup/contexts/AuthContext.jsx
@@ -0,0 +1,84 @@
+import React, { createContext, useContext, useState, useEffect } from 'react';
+
+const AuthContext = createContext(null);
+
+export function AuthProvider({ children }) {
+ const [user, setUser] = useState(null);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ // Check for stored session on mount
+ const stored = localStorage.getItem('aethex_user');
+ if (stored) {
+ try {
+ setUser(JSON.parse(stored));
+ } catch (e) {
+ localStorage.removeItem('aethex_user');
+ }
+ }
+ setLoading(false);
+ }, []);
+
+ const login = (userData) => {
+ const userWithDefaults = {
+ id: Date.now(),
+ email: userData.email,
+ username: userData.username || userData.email.split('@')[0],
+ displayName: userData.displayName || userData.email.split('@')[0],
+ avatar: null,
+ createdAt: new Date().toISOString(),
+ ...userData,
+ };
+ setUser(userWithDefaults);
+ localStorage.setItem('aethex_user', JSON.stringify(userWithDefaults));
+ };
+
+ const register = (userData) => {
+ const newUser = {
+ id: Date.now(),
+ email: userData.email,
+ username: userData.username,
+ displayName: userData.displayName || userData.username,
+ avatar: null,
+ createdAt: new Date().toISOString(),
+ ...userData,
+ };
+ setUser(newUser);
+ localStorage.setItem('aethex_user', JSON.stringify(newUser));
+ };
+
+ const logout = () => {
+ setUser(null);
+ localStorage.removeItem('aethex_user');
+ };
+
+ const updateUser = (updates) => {
+ const updated = { ...user, ...updates };
+ setUser(updated);
+ localStorage.setItem('aethex_user', JSON.stringify(updated));
+ };
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useAuth() {
+ const context = useContext(AuthContext);
+ if (!context) {
+ throw new Error('useAuth must be used within an AuthProvider');
+ }
+ return context;
+}
+
+export default AuthContext;
diff --git a/src/frontend/mockup/index.jsx b/src/frontend/mockup/index.jsx
index 926f79f..ee39812 100644
--- a/src/frontend/mockup/index.jsx
+++ b/src/frontend/mockup/index.jsx
@@ -1,13 +1,92 @@
import React from "react";
import { createRoot } from "react-dom/client";
+import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
+import { AuthProvider, useAuth } from "./contexts/AuthContext";
import MainLayout from "./MainLayout";
+import LandingPage from "./pages/LandingPage";
+import LoginPage from "./pages/LoginPage";
+import RegisterPage from "./pages/RegisterPage";
+import AboutPage from "./pages/AboutPage";
+import FeaturesPage from "./pages/FeaturesPage";
import "./global.css";
+// Protected route wrapper
+function ProtectedRoute({ children }) {
+ const { isAuthenticated, loading } = useAuth();
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ return isAuthenticated ? children :
;
+}
+
+// Public route - redirect to app if already logged in
+function PublicRoute({ children }) {
+ const { isAuthenticated, loading } = useAuth();
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ return !isAuthenticated ? children :
;
+}
+
+function App() {
+ const { login, register, logout } = useAuth();
+
+ return (
+
+ {/* Public pages */}
+ } />
+ } />
+ } />
+
+ {/* Auth pages */}
+
+
+
+ } />
+
+
+
+ } />
+
+ {/* Protected app */}
+
+
+
+ } />
+
+ {/* Fallback */}
+ } />
+
+ );
+}
+
const root = document.getElementById("root");
if (root) {
createRoot(root).render(
-
+
+
+
+
+
);
}
+
diff --git a/src/frontend/mockup/mockup.css b/src/frontend/mockup/mockup.css
new file mode 100644
index 0000000..93f0010
--- /dev/null
+++ b/src/frontend/mockup/mockup.css
@@ -0,0 +1,10311 @@
+/* AeThex Connect Mockup Styles - Exact Match */
+@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@300;400;500;700&display=swap');
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: 'Roboto Mono', monospace;
+ background: #0a0a0a;
+ color: #e0e0e0;
+ overflow: hidden;
+ height: 100vh;
+}
+
+/* Scanline effect */
+.connect-container::before {
+ content: '';
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: repeating-linear-gradient(
+ 0deg,
+ rgba(0, 0, 0, 0.15),
+ rgba(0, 0, 0, 0.15) 1px,
+ transparent 1px,
+ transparent 2px
+ );
+ pointer-events: none;
+ z-index: 1000;
+}
+
+/* Main Layout */
+.connect-container {
+ display: flex;
+ height: 100vh;
+ position: relative;
+}
+
+/* Server Sidebar */
+.server-list {
+ width: 80px;
+ min-width: 80px;
+ background: #0d0d0d;
+ border-right: 1px solid #1a1a1a;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 12px 0;
+ gap: 12px;
+}
+
+.server-icon {
+ width: 56px;
+ height: 56px;
+ background: #1a1a1a;
+ border-radius: 16px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: 700;
+ font-size: 1.2em;
+ cursor: pointer;
+ transition: all 0.3s;
+ position: relative;
+ color: #e0e0e0;
+}
+
+.server-icon:hover {
+ border-radius: 12px;
+ transform: translateY(-2px);
+}
+
+.server-icon.active {
+ border-radius: 12px;
+}
+
+.server-icon::before {
+ content: '';
+ position: absolute;
+ left: -12px;
+ width: 4px;
+ height: 0;
+ transition: height 0.3s;
+ border-radius: 0 4px 4px 0;
+}
+
+.server-icon.active::before {
+ height: 40px;
+}
+
+.server-icon.foundation {
+ background: linear-gradient(135deg, #ff0000 0%, #990000 100%);
+}
+
+.server-icon.foundation.active::before {
+ background: #ff0000;
+}
+
+.server-icon.corporation {
+ background: linear-gradient(135deg, #0066ff 0%, #003380 100%);
+}
+
+.server-icon.corporation.active::before {
+ background: #0066ff;
+}
+
+.server-icon.labs {
+ background: linear-gradient(135deg, #ffa500 0%, #ff8c00 100%);
+}
+
+.server-icon.labs.active::before {
+ background: #ffa500;
+}
+
+.server-icon.community {
+ background: #1a1a1a;
+ color: #666;
+}
+
+.server-divider {
+ width: 40px;
+ height: 2px;
+ background: #1a1a1a;
+ margin: 4px 0;
+}
+
+/* Channel Sidebar */
+.channel-sidebar {
+ width: 280px;
+ min-width: 280px;
+ background: #0f0f0f;
+ border-right: 1px solid #1a1a1a;
+ display: flex;
+ flex-direction: column;
+}
+
+.server-header {
+ padding: 16px;
+ border-bottom: 1px solid #1a1a1a;
+ font-weight: 700;
+ font-size: 1.1em;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.server-badge {
+ font-size: 0.7em;
+ padding: 4px 8px;
+ border-radius: 4px;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+}
+
+.server-badge.foundation {
+ background: rgba(255, 0, 0, 0.2);
+ color: #ff0000;
+ border: 1px solid #ff0000;
+}
+
+.server-badge.corporation {
+ background: rgba(0, 102, 255, 0.2);
+ color: #0066ff;
+ border: 1px solid #0066ff;
+}
+
+.server-badge.labs {
+ background: rgba(255, 165, 0, 0.2);
+ color: #ffa500;
+ border: 1px solid #ffa500;
+}
+
+.channel-list {
+ flex: 1;
+ overflow-y: auto;
+ padding: 8px 0;
+}
+
+.channel-category {
+ padding: 16px 16px 8px 16px;
+ font-size: 0.75em;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ color: #666;
+ font-weight: 700;
+}
+
+.channel-item {
+ padding: 8px 16px;
+ margin: 2px 8px;
+ border-radius: 4px;
+ cursor: pointer;
+ transition: all 0.2s;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 0.95em;
+}
+
+.channel-item:hover {
+ background: #1a1a1a;
+}
+
+.channel-item.active {
+ background: #1a1a1a;
+}
+
+.channel-icon {
+ color: #666;
+}
+
+.channel-name {
+ flex: 1;
+}
+
+.channel-badge {
+ font-size: 0.75em;
+ background: #ff0000;
+ color: #fff;
+ padding: 2px 6px;
+ border-radius: 10px;
+}
+
+/* User Presence Panel */
+.user-presence {
+ padding: 12px 16px;
+ border-top: 1px solid #1a1a1a;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ font-size: 0.9em;
+}
+
+.user-avatar {
+ width: 40px;
+ height: 40px;
+ background: linear-gradient(135deg, #ff0000, #0066ff, #ffa500);
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: 700;
+}
+
+.user-info {
+ flex: 1;
+}
+
+.user-name {
+ font-weight: 700;
+ margin-bottom: 2px;
+}
+
+.user-status {
+ font-size: 0.85em;
+ color: #666;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.status-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: #00ff00;
+ box-shadow: 0 0 8px #00ff00;
+}
+
+/* Chat Area */
+.chat-area {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ background: #0a0a0a;
+ min-width: 0;
+}
+
+.chat-header {
+ padding: 16px 20px;
+ border-bottom: 1px solid #1a1a1a;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.channel-name-header {
+ flex: 1;
+ font-weight: 700;
+ font-size: 1.1em;
+}
+
+.chat-tools {
+ display: flex;
+ gap: 16px;
+ font-size: 0.9em;
+ color: #666;
+}
+
+.chat-tool {
+ cursor: pointer;
+ transition: color 0.2s;
+}
+
+.chat-tool:hover {
+ color: #0066ff;
+}
+
+.chat-messages {
+ flex: 1;
+ overflow-y: auto;
+ padding: 20px;
+}
+
+.message {
+ display: flex;
+ gap: 16px;
+ margin-bottom: 20px;
+ padding: 12px;
+ border-radius: 4px;
+ transition: background 0.2s;
+}
+
+.message:hover {
+ background: #0f0f0f;
+}
+
+.message-avatar {
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ background: #1a1a1a;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: 700;
+ font-size: 0.9em;
+ flex-shrink: 0;
+}
+
+.message-content {
+ flex: 1;
+ min-width: 0;
+}
+
+.message-header {
+ display: flex;
+ align-items: baseline;
+ gap: 12px;
+ margin-bottom: 4px;
+ flex-wrap: wrap;
+}
+
+.message-author {
+ font-weight: 700;
+}
+
+.message-time {
+ font-size: 0.75em;
+ color: #666;
+}
+
+.message-badge {
+ font-size: 0.65em;
+ padding: 2px 6px;
+ border-radius: 3px;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+}
+
+.message-badge.foundation {
+ background: rgba(255, 0, 0, 0.2);
+ color: #ff0000;
+}
+
+.message-badge.corporation {
+ background: rgba(0, 102, 255, 0.2);
+ color: #0066ff;
+}
+
+.message-badge.labs {
+ background: rgba(255, 165, 0, 0.2);
+ color: #ffa500;
+}
+
+.message-text {
+ line-height: 1.6;
+ color: #ccc;
+ word-wrap: break-word;
+}
+
+.message-system {
+ background: #0f0f0f;
+ border-left: 3px solid;
+ padding: 12px;
+ margin-bottom: 16px;
+ font-size: 0.9em;
+}
+
+.message-system.foundation {
+ border-color: #ff0000;
+}
+
+.message-system.corporation {
+ border-color: #0066ff;
+}
+
+.message-system.labs {
+ border-color: #ffa500;
+}
+
+.system-label {
+ font-size: 0.75em;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ margin-bottom: 6px;
+ font-weight: 700;
+}
+
+.system-label.foundation { color: #ff0000; }
+.system-label.corporation { color: #0066ff; }
+.system-label.labs { color: #ffa500; }
+
+/* Message Input */
+.message-input-container {
+ padding: 20px;
+ border-top: 1px solid #1a1a1a;
+}
+
+.message-input {
+ background: #0f0f0f;
+ border: 1px solid #1a1a1a;
+ border-radius: 8px;
+ padding: 12px 16px;
+ width: 100%;
+ color: #e0e0e0;
+ font-family: 'Roboto Mono', monospace;
+ font-size: 0.95em;
+ transition: border-color 0.3s;
+}
+
+.message-input:focus {
+ outline: none;
+ border-color: #0066ff;
+}
+
+.message-input::placeholder {
+ color: #666;
+}
+
+/* Member Sidebar */
+.member-sidebar {
+ width: 280px;
+ min-width: 280px;
+ background: #0f0f0f;
+ border-left: 1px solid #1a1a1a;
+ display: flex;
+ flex-direction: column;
+}
+
+.member-header {
+ padding: 16px;
+ border-bottom: 1px solid #1a1a1a;
+ font-size: 0.85em;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ color: #666;
+}
+
+.member-list {
+ flex: 1;
+ overflow-y: auto;
+ padding: 12px 0;
+}
+
+.member-section {
+ margin-bottom: 16px;
+}
+
+.member-section-title {
+ padding: 8px 16px;
+ font-size: 0.75em;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ color: #666;
+ font-weight: 700;
+}
+
+.member-item {
+ padding: 6px 16px;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ cursor: pointer;
+ transition: background 0.2s;
+}
+
+.member-item:hover {
+ background: #1a1a1a;
+}
+
+.member-avatar-small {
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ background: #1a1a1a;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.8em;
+ font-weight: 700;
+ position: relative;
+}
+
+.online-indicator {
+ position: absolute;
+ bottom: -2px;
+ right: -2px;
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ border: 2px solid #0f0f0f;
+}
+
+.online-indicator.online { background: #00ff00; }
+.online-indicator.in-game { background: #0066ff; }
+.online-indicator.labs { background: #ffa500; }
+
+.member-name {
+ flex: 1;
+ font-size: 0.9em;
+}
+
+.member-activity {
+ font-size: 0.75em;
+ color: #666;
+}
+
+/* ==================== NEW DISCORD-LIKE COMPONENTS ==================== */
+
+/* Server List Enhancements */
+.notification-badge {
+ position: absolute;
+ bottom: -4px;
+ right: -4px;
+ background: #ed4245;
+ color: white;
+ font-size: 0.65em;
+ padding: 2px 5px;
+ border-radius: 10px;
+ min-width: 18px;
+ text-align: center;
+ font-weight: 700;
+ border: 2px solid #0d0d0d;
+}
+
+.update-dot {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ width: 10px;
+ height: 10px;
+ background: #3ba55d;
+ border-radius: 50%;
+ border: 2px solid #0d0d0d;
+}
+
+.server-tooltip {
+ position: absolute;
+ left: 70px;
+ background: #18191c;
+ color: #fff;
+ padding: 8px 12px;
+ border-radius: 4px;
+ font-size: 0.85em;
+ white-space: nowrap;
+ z-index: 1001;
+ box-shadow: 0 2px 10px rgba(0,0,0,0.5);
+}
+
+.server-tooltip::before {
+ content: '';
+ position: absolute;
+ left: -6px;
+ top: 50%;
+ transform: translateY(-50%);
+ border: 6px solid transparent;
+ border-right-color: #18191c;
+}
+
+/* User Settings Bar */
+.user-settings-bar {
+ padding: 8px;
+ background: #0d0d0d;
+ border-top: 1px solid #1a1a1a;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.user-info-section {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ cursor: pointer;
+ padding: 4px 8px;
+ border-radius: 4px;
+ position: relative;
+}
+
+.user-info-section:hover {
+ background: #1a1a1a;
+}
+
+.user-avatar-container {
+ position: relative;
+}
+
+.user-avatar-small {
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: 700;
+ font-size: 0.8em;
+}
+
+.status-indicator {
+ position: absolute;
+ bottom: -2px;
+ right: -2px;
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ border: 2px solid #0d0d0d;
+}
+
+.user-details {
+ display: flex;
+ flex-direction: column;
+}
+
+.user-display-name {
+ font-size: 0.85em;
+ font-weight: 600;
+}
+
+.user-tag {
+ font-size: 0.7em;
+ color: #666;
+}
+
+.status-menu {
+ position: absolute;
+ bottom: 100%;
+ left: 0;
+ background: #18191c;
+ border-radius: 4px;
+ padding: 8px 0;
+ min-width: 180px;
+ box-shadow: 0 4px 20px rgba(0,0,0,0.5);
+ z-index: 100;
+}
+
+.status-option {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 12px;
+ cursor: pointer;
+ font-size: 0.85em;
+}
+
+.status-option:hover {
+ background: #1a1a1a;
+}
+
+.status-divider {
+ height: 1px;
+ background: #2f3136;
+ margin: 4px 8px;
+}
+
+.user-controls {
+ display: flex;
+ gap: 4px;
+}
+
+.control-button {
+ width: 32px;
+ height: 32px;
+ border: none;
+ background: transparent;
+ border-radius: 4px;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1em;
+ position: relative;
+}
+
+.control-button:hover {
+ background: #1a1a1a;
+}
+
+.control-button.active {
+ color: #ed4245;
+}
+
+.slash-overlay {
+ position: absolute;
+ width: 100%;
+ height: 2px;
+ background: #ed4245;
+ transform: rotate(-45deg);
+}
+
+/* Voice Channel */
+.voice-channel-container {
+ margin: 4px 8px;
+}
+
+.voice-channel-header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 0.9em;
+}
+
+.voice-channel-header:hover {
+ background: #1a1a1a;
+}
+
+.voice-channel-name {
+ flex: 1;
+}
+
+.voice-user-count {
+ font-size: 0.8em;
+ color: #666;
+}
+
+.expand-icon {
+ font-size: 0.7em;
+ color: #666;
+}
+
+.voice-users-list {
+ margin-left: 24px;
+}
+
+.voice-user {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 4px 8px;
+ border-radius: 4px;
+ font-size: 0.85em;
+}
+
+.voice-user:hover {
+ background: #1a1a1a;
+}
+
+.voice-user.speaking {
+ background: rgba(59, 165, 93, 0.1);
+}
+
+.voice-user-avatar {
+ width: 24px;
+ height: 24px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.7em;
+ font-weight: 700;
+ position: relative;
+}
+
+.speaking-ring {
+ position: absolute;
+ inset: -3px;
+ border: 2px solid #3ba55d;
+ border-radius: 50%;
+ animation: pulse-ring 1s infinite;
+}
+
+@keyframes pulse-ring {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.5; }
+}
+
+.voice-user-name {
+ flex: 1;
+}
+
+.voice-user-icons {
+ display: flex;
+ gap: 4px;
+ font-size: 0.8em;
+}
+
+.voice-connected-panel {
+ background: #1a1a1a;
+ border-radius: 4px;
+ margin: 8px;
+ padding: 12px;
+}
+
+.voice-status {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 8px;
+}
+
+.connected-text {
+ color: #3ba55d;
+ font-size: 0.9em;
+ font-weight: 600;
+}
+
+.voice-ping {
+ font-size: 0.8em;
+ color: #666;
+}
+
+.voice-controls {
+ display: flex;
+ gap: 8px;
+ justify-content: center;
+}
+
+.voice-control-btn {
+ width: 36px;
+ height: 36px;
+ border: none;
+ background: #2f3136;
+ border-radius: 50%;
+ cursor: pointer;
+ font-size: 1em;
+}
+
+.voice-control-btn:hover {
+ background: #40444b;
+}
+
+/* Emoji Picker */
+.emoji-picker {
+ position: absolute;
+ bottom: 100%;
+ right: 0;
+ width: 350px;
+ background: #18191c;
+ border-radius: 8px;
+ box-shadow: 0 4px 20px rgba(0,0,0,0.5);
+ z-index: 100;
+ overflow: hidden;
+}
+
+.emoji-picker-header {
+ padding: 12px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.emoji-search {
+ width: 100%;
+ padding: 8px 12px;
+ background: #0f0f0f;
+ border: none;
+ border-radius: 4px;
+ color: #e0e0e0;
+ font-family: inherit;
+}
+
+.emoji-categories {
+ display: flex;
+ padding: 8px;
+ gap: 4px;
+ border-bottom: 1px solid #2f3136;
+ overflow-x: auto;
+}
+
+.emoji-category-btn {
+ width: 32px;
+ height: 32px;
+ border: none;
+ background: transparent;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 1.1em;
+}
+
+.emoji-category-btn:hover {
+ background: #2f3136;
+}
+
+.emoji-category-btn.active {
+ background: #2f3136;
+}
+
+.emoji-grid {
+ display: grid;
+ grid-template-columns: repeat(8, 1fr);
+ gap: 4px;
+ padding: 12px;
+ max-height: 250px;
+ overflow-y: auto;
+}
+
+.emoji-btn {
+ width: 32px;
+ height: 32px;
+ border: none;
+ background: transparent;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 1.3em;
+}
+
+.emoji-btn:hover {
+ background: #2f3136;
+}
+
+.emoji-picker-footer {
+ padding: 8px 12px;
+ border-top: 1px solid #2f3136;
+ font-size: 0.8em;
+ color: #666;
+}
+
+/* Message Actions */
+.message-actions {
+ position: absolute;
+ top: -20px;
+ right: 16px;
+ display: flex;
+ gap: 8px;
+ background: #18191c;
+ border-radius: 4px;
+ padding: 4px;
+ box-shadow: 0 2px 10px rgba(0,0,0,0.3);
+ opacity: 0;
+ transition: opacity 0.2s;
+}
+
+.message:hover .message-actions {
+ opacity: 1;
+}
+
+.quick-reactions {
+ display: flex;
+ gap: 2px;
+}
+
+.reaction-btn {
+ width: 28px;
+ height: 28px;
+ border: none;
+ background: transparent;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 0.9em;
+}
+
+.reaction-btn:hover {
+ background: #2f3136;
+}
+
+.action-buttons {
+ display: flex;
+ gap: 2px;
+ border-left: 1px solid #2f3136;
+ padding-left: 8px;
+ margin-left: 8px;
+}
+
+.action-btn {
+ width: 28px;
+ height: 28px;
+ border: none;
+ background: transparent;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 0.9em;
+}
+
+.action-btn:hover {
+ background: #2f3136;
+}
+
+.more-menu {
+ position: absolute;
+ top: 100%;
+ right: 0;
+ background: #18191c;
+ border-radius: 4px;
+ padding: 8px 0;
+ min-width: 180px;
+ box-shadow: 0 4px 20px rgba(0,0,0,0.5);
+ z-index: 100;
+}
+
+.menu-item {
+ padding: 8px 12px;
+ cursor: pointer;
+ font-size: 0.85em;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.menu-item:hover {
+ background: #2f3136;
+}
+
+.menu-item.danger {
+ color: #ed4245;
+}
+
+.menu-divider {
+ height: 1px;
+ background: #2f3136;
+ margin: 4px 8px;
+}
+
+/* Typing Indicator */
+.typing-indicator {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 20px;
+ font-size: 0.85em;
+ color: #666;
+}
+
+.typing-dots {
+ display: flex;
+ gap: 3px;
+}
+
+.typing-dots .dot {
+ width: 6px;
+ height: 6px;
+ background: #666;
+ border-radius: 50%;
+ animation: typing-bounce 1.4s infinite ease-in-out;
+}
+
+.typing-dots .dot:nth-child(1) { animation-delay: 0s; }
+.typing-dots .dot:nth-child(2) { animation-delay: 0.2s; }
+.typing-dots .dot:nth-child(3) { animation-delay: 0.4s; }
+
+@keyframes typing-bounce {
+ 0%, 60%, 100% { transform: translateY(0); }
+ 30% { transform: translateY(-4px); }
+}
+
+/* User Profile Card */
+.user-profile-card {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 340px;
+ background: #18191c;
+ border-radius: 8px;
+ overflow: hidden;
+ box-shadow: 0 8px 40px rgba(0,0,0,0.5);
+ z-index: 1000;
+}
+
+.profile-banner {
+ height: 60px;
+}
+
+.profile-avatar-section {
+ display: flex;
+ align-items: flex-end;
+ justify-content: space-between;
+ padding: 0 16px;
+ margin-top: -40px;
+}
+
+.profile-avatar-wrapper {
+ position: relative;
+}
+
+.profile-avatar {
+ width: 80px;
+ height: 80px;
+ border-radius: 50%;
+ border: 6px solid #18191c;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 2em;
+ font-weight: 700;
+}
+
+.profile-status-indicator {
+ position: absolute;
+ bottom: 4px;
+ right: 4px;
+ width: 20px;
+ height: 20px;
+ border-radius: 50%;
+ border: 4px solid #18191c;
+}
+
+.profile-badges {
+ display: flex;
+ gap: 4px;
+}
+
+.profile-badge {
+ width: 24px;
+ height: 24px;
+ border-radius: 4px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.7em;
+ font-weight: 700;
+}
+
+.profile-info {
+ padding: 16px;
+}
+
+.profile-name {
+ font-size: 1.3em;
+ font-weight: 700;
+ margin-bottom: 4px;
+}
+
+.profile-tag {
+ color: #666;
+ font-size: 0.9em;
+}
+
+.profile-custom-status {
+ margin-top: 8px;
+ padding: 8px;
+ background: #0f0f0f;
+ border-radius: 4px;
+ font-size: 0.85em;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.profile-divider {
+ height: 1px;
+ background: #2f3136;
+ margin: 0 16px;
+}
+
+.profile-section {
+ padding: 12px 16px;
+}
+
+.section-title {
+ font-size: 0.7em;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ color: #b9bbbe;
+ font-weight: 700;
+ margin-bottom: 8px;
+}
+
+.activity-card {
+ display: flex;
+ gap: 12px;
+ padding: 8px;
+ background: #0f0f0f;
+ border-radius: 4px;
+}
+
+.activity-icon {
+ font-size: 1.5em;
+}
+
+.activity-info {
+ flex: 1;
+}
+
+.activity-name {
+ font-weight: 600;
+ font-size: 0.9em;
+}
+
+.activity-details,
+.activity-elapsed {
+ font-size: 0.8em;
+ color: #666;
+}
+
+.about-text,
+.member-since {
+ font-size: 0.85em;
+ color: #b9bbbe;
+ line-height: 1.4;
+}
+
+.roles-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+}
+
+.role-tag {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ padding: 4px 8px;
+ border: 1px solid;
+ border-radius: 4px;
+ font-size: 0.8em;
+}
+
+.role-dot {
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+}
+
+.note-input {
+ width: 100%;
+ padding: 8px;
+ background: #0f0f0f;
+ border: none;
+ border-radius: 4px;
+ color: #e0e0e0;
+ font-family: inherit;
+ font-size: 0.85em;
+ resize: none;
+}
+
+.profile-actions {
+ display: flex;
+ gap: 8px;
+ padding: 12px 16px;
+ border-top: 1px solid #2f3136;
+}
+
+.profile-btn {
+ flex: 1;
+ padding: 8px;
+ border: none;
+ border-radius: 4px;
+ background: #2f3136;
+ color: #e0e0e0;
+ font-family: inherit;
+ cursor: pointer;
+ font-size: 0.85em;
+}
+
+.profile-btn:hover {
+ background: #40444b;
+}
+
+.profile-btn.primary {
+ background: #5865f2;
+}
+
+.profile-btn.primary:hover {
+ background: #4752c4;
+}
+
+/* Search Panel */
+.search-panel {
+ position: fixed;
+ top: 0;
+ right: 0;
+ width: 420px;
+ height: 100vh;
+ background: #18191c;
+ border-left: 1px solid #2f3136;
+ z-index: 100;
+ display: flex;
+ flex-direction: column;
+}
+
+.search-header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 16px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.search-form {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ background: #0f0f0f;
+ padding: 8px 12px;
+ border-radius: 4px;
+}
+
+.search-input {
+ flex: 1;
+ border: none;
+ background: transparent;
+ color: #e0e0e0;
+ font-family: inherit;
+ font-size: 0.9em;
+}
+
+.search-input::placeholder {
+ color: #666;
+}
+
+.clear-btn,
+.close-search {
+ border: none;
+ background: transparent;
+ color: #666;
+ cursor: pointer;
+ font-size: 1em;
+}
+
+.close-search:hover {
+ color: #e0e0e0;
+}
+
+.search-filters {
+ display: flex;
+ gap: 4px;
+ padding: 12px 16px;
+ overflow-x: auto;
+ border-bottom: 1px solid #2f3136;
+}
+
+.filter-btn {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ padding: 6px 12px;
+ border: none;
+ background: #2f3136;
+ border-radius: 16px;
+ color: #b9bbbe;
+ font-family: inherit;
+ font-size: 0.8em;
+ cursor: pointer;
+ white-space: nowrap;
+}
+
+.filter-btn:hover,
+.filter-btn.active {
+ background: #5865f2;
+ color: #fff;
+}
+
+.search-content {
+ flex: 1;
+ overflow-y: auto;
+ padding: 16px;
+}
+
+.search-suggestions h4,
+.search-results h4 {
+ font-size: 0.75em;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ color: #666;
+ margin-bottom: 12px;
+}
+
+.recent-search {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 0.9em;
+}
+
+.recent-search:hover {
+ background: #2f3136;
+}
+
+.search-options {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-bottom: 20px;
+}
+
+.search-options .option {
+ font-size: 0.8em;
+ padding: 4px 8px;
+ background: #2f3136;
+ border-radius: 4px;
+}
+
+.search-options code {
+ color: #5865f2;
+}
+
+.search-loading {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 12px;
+ padding: 40px;
+}
+
+.spinner {
+ width: 32px;
+ height: 32px;
+ border: 3px solid #2f3136;
+ border-top-color: #5865f2;
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+ to { transform: rotate(360deg); }
+}
+
+.search-result {
+ display: flex;
+ gap: 12px;
+ padding: 12px;
+ border-radius: 4px;
+ cursor: pointer;
+}
+
+.search-result:hover {
+ background: #2f3136;
+}
+
+.result-avatar {
+ width: 36px;
+ height: 36px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: 700;
+ font-size: 0.9em;
+ flex-shrink: 0;
+}
+
+.result-icon {
+ width: 36px;
+ height: 36px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.5em;
+}
+
+.result-content {
+ flex: 1;
+ min-width: 0;
+}
+
+.result-header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 4px;
+ font-size: 0.85em;
+}
+
+.result-author {
+ font-weight: 600;
+}
+
+.result-channel,
+.result-time {
+ color: #666;
+}
+
+.result-text {
+ font-size: 0.85em;
+ color: #b9bbbe;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.result-filename {
+ font-weight: 600;
+ font-size: 0.9em;
+}
+
+.result-meta {
+ font-size: 0.8em;
+ color: #666;
+}
+
+/* Thread Panel */
+.thread-panel {
+ position: fixed;
+ top: 0;
+ right: 0;
+ width: 420px;
+ height: 100vh;
+ background: #0f0f0f;
+ border-left: 1px solid #2f3136;
+ z-index: 100;
+ display: flex;
+ flex-direction: column;
+}
+
+.thread-header {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 16px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.thread-title {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ flex: 1;
+ font-weight: 600;
+}
+
+.thread-meta {
+ display: flex;
+ gap: 8px;
+ font-size: 0.8em;
+ color: #666;
+}
+
+.thread-close {
+ border: none;
+ background: transparent;
+ color: #666;
+ cursor: pointer;
+ font-size: 1.2em;
+}
+
+.thread-close:hover {
+ color: #e0e0e0;
+}
+
+.thread-parent {
+ padding: 16px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.parent-message {
+ display: flex;
+ gap: 12px;
+}
+
+.thread-line {
+ width: 2px;
+ height: 20px;
+ background: #2f3136;
+ margin-left: 20px;
+ margin-top: 8px;
+}
+
+.thread-replies {
+ flex: 1;
+ overflow-y: auto;
+ padding: 16px;
+}
+
+.thread-reply {
+ display: flex;
+ gap: 12px;
+ margin-bottom: 16px;
+}
+
+.message-avatar.small {
+ width: 32px;
+ height: 32px;
+ font-size: 0.8em;
+}
+
+.thread-input-container {
+ display: flex;
+ gap: 8px;
+ padding: 16px;
+ border-top: 1px solid #2f3136;
+}
+
+.thread-input {
+ flex: 1;
+ padding: 12px;
+ background: #1a1a1a;
+ border: none;
+ border-radius: 4px;
+ color: #e0e0e0;
+ font-family: inherit;
+}
+
+.thread-send {
+ padding: 12px 16px;
+ background: #5865f2;
+ border: none;
+ border-radius: 4px;
+ color: white;
+ cursor: pointer;
+}
+
+.thread-send:hover {
+ background: #4752c4;
+}
+
+/* Server Settings Modal */
+.modal-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(0,0,0,0.85);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+}
+
+.server-settings-modal {
+ width: 90%;
+ max-width: 900px;
+ height: 80vh;
+ background: #18191c;
+ border-radius: 8px;
+ display: flex;
+ overflow: hidden;
+}
+
+.settings-sidebar {
+ width: 240px;
+ background: #0f0f0f;
+ border-right: 1px solid #2f3136;
+ display: flex;
+ flex-direction: column;
+}
+
+.settings-server-info {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 16px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.settings-server-icon {
+ width: 48px;
+ height: 48px;
+ border-radius: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: 700;
+ font-size: 1.2em;
+}
+
+.settings-server-name {
+ font-weight: 600;
+ font-size: 0.95em;
+}
+
+.settings-nav {
+ flex: 1;
+ overflow-y: auto;
+ padding: 8px;
+}
+
+.settings-nav-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 12px;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 0.9em;
+ margin-bottom: 2px;
+}
+
+.settings-nav-item:hover {
+ background: #2f3136;
+}
+
+.settings-nav-item.active {
+ background: #2f3136;
+ color: #fff;
+}
+
+.settings-nav-item.danger {
+ color: #ed4245;
+}
+
+.settings-divider {
+ height: 1px;
+ background: #2f3136;
+ margin: 8px 0;
+}
+
+.nav-icon {
+ font-size: 1em;
+}
+
+.settings-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+.settings-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px 24px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.settings-header h2 {
+ font-size: 1.2em;
+}
+
+.settings-close {
+ border: none;
+ background: transparent;
+ color: #666;
+ cursor: pointer;
+ font-size: 1.5em;
+}
+
+.settings-close:hover {
+ color: #e0e0e0;
+}
+
+.settings-overview {
+ padding: 24px;
+ overflow-y: auto;
+}
+
+.setting-group {
+ margin-bottom: 24px;
+}
+
+.setting-group label {
+ display: block;
+ font-size: 0.75em;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ color: #b9bbbe;
+ font-weight: 700;
+ margin-bottom: 8px;
+}
+
+.setting-input,
+.setting-textarea,
+.setting-select {
+ width: 100%;
+ padding: 12px;
+ background: #0f0f0f;
+ border: 1px solid #2f3136;
+ border-radius: 4px;
+ color: #e0e0e0;
+ font-family: inherit;
+ font-size: 0.95em;
+}
+
+.setting-input:focus,
+.setting-textarea:focus,
+.setting-select:focus {
+ outline: none;
+ border-color: #5865f2;
+}
+
+.icon-upload {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+}
+
+.current-icon {
+ width: 64px;
+ height: 64px;
+ border-radius: 16px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: 700;
+ font-size: 1.5em;
+}
+
+.upload-btn {
+ padding: 10px 16px;
+ background: #5865f2;
+ border: none;
+ border-radius: 4px;
+ color: white;
+ cursor: pointer;
+ font-family: inherit;
+}
+
+.upload-btn:hover {
+ background: #4752c4;
+}
+
+.boost-info {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ padding: 16px;
+ background: linear-gradient(135deg, rgba(255,115,250,0.1), rgba(85,62,223,0.1));
+ border-radius: 8px;
+ border: 1px solid rgba(255,115,250,0.3);
+}
+
+.boost-level {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-weight: 600;
+}
+
+.boost-count {
+ color: #666;
+ font-size: 0.9em;
+}
+
+.settings-roles {
+ padding: 24px;
+ overflow-y: auto;
+}
+
+.roles-header {
+ margin-bottom: 16px;
+}
+
+.create-role-btn {
+ padding: 10px 16px;
+ background: #5865f2;
+ border: none;
+ border-radius: 4px;
+ color: white;
+ cursor: pointer;
+ font-family: inherit;
+}
+
+.create-role-btn:hover {
+ background: #4752c4;
+}
+
+.role-item {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 12px;
+ border-radius: 4px;
+ cursor: pointer;
+}
+
+.role-item:hover {
+ background: #2f3136;
+}
+
+.role-color {
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+}
+
+.role-name {
+ flex: 1;
+ font-weight: 500;
+}
+
+.role-members {
+ font-size: 0.85em;
+ color: #666;
+}
+
+.settings-placeholder {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ color: #666;
+}
+
+.placeholder-icon {
+ font-size: 3em;
+ margin-bottom: 16px;
+}
+
+/* ==================== DMs VIEW ==================== */
+.dms-view {
+ display: flex;
+ flex: 1;
+ height: 100%;
+}
+
+.dms-sidebar {
+ width: 240px;
+ background: #0f0f0f;
+ border-right: 1px solid #2f3136;
+ display: flex;
+ flex-direction: column;
+}
+
+.dms-search {
+ padding: 12px;
+}
+
+.dms-search-input {
+ width: 100%;
+ padding: 8px 12px;
+ background: #0a0a0a;
+ border: none;
+ border-radius: 4px;
+ color: #e0e0e0;
+ font-family: inherit;
+ font-size: 0.85em;
+}
+
+.dms-section {
+ padding: 8px;
+}
+
+.dms-section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 8px;
+ font-size: 0.75em;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ color: #666;
+}
+
+.add-dm-btn {
+ background: none;
+ border: none;
+ color: #666;
+ cursor: pointer;
+ font-size: 1.2em;
+}
+
+.add-dm-btn:hover {
+ color: #e0e0e0;
+}
+
+.dms-nav-item {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 10px 12px;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 0.9em;
+}
+
+.dms-nav-item:hover {
+ background: #1a1a1a;
+}
+
+.dm-item {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 8px 12px;
+ border-radius: 4px;
+ cursor: pointer;
+ position: relative;
+}
+
+.dm-item:hover {
+ background: #1a1a1a;
+}
+
+.dm-avatar {
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: 700;
+ font-size: 0.85em;
+ position: relative;
+}
+
+.dm-status {
+ position: absolute;
+ bottom: -2px;
+ right: -2px;
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ border: 2px solid #0f0f0f;
+}
+
+.dm-info {
+ flex: 1;
+ min-width: 0;
+}
+
+.dm-name {
+ font-size: 0.9em;
+ font-weight: 500;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.group-count {
+ font-size: 0.75em;
+ color: #666;
+}
+
+.dm-preview {
+ font-size: 0.75em;
+ color: #666;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.dm-unread {
+ background: #ed4245;
+ color: white;
+ font-size: 0.7em;
+ padding: 2px 6px;
+ border-radius: 10px;
+ font-weight: 700;
+}
+
+.dm-close {
+ position: absolute;
+ top: 50%;
+ right: 8px;
+ transform: translateY(-50%);
+ background: none;
+ border: none;
+ color: #666;
+ cursor: pointer;
+ opacity: 0;
+ font-size: 0.8em;
+}
+
+.dm-item:hover .dm-close {
+ opacity: 1;
+}
+
+/* Friends Panel */
+.friends-panel {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ background: #0a0a0a;
+}
+
+.friends-header {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ padding: 16px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.friends-title {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-weight: 600;
+}
+
+.friends-tabs {
+ display: flex;
+ gap: 8px;
+}
+
+.friends-tab {
+ padding: 4px 12px;
+ background: none;
+ border: none;
+ border-radius: 4px;
+ color: #b9bbbe;
+ font-family: inherit;
+ font-size: 0.85em;
+ cursor: pointer;
+}
+
+.friends-tab:hover {
+ background: #2f3136;
+ color: #e0e0e0;
+}
+
+.friends-tab.active {
+ background: #2f3136;
+ color: #e0e0e0;
+}
+
+.friends-tab.action {
+ background: #3ba55d;
+ color: white;
+}
+
+.tab-count {
+ background: #ed4245;
+ color: white;
+ font-size: 0.7em;
+ padding: 1px 5px;
+ border-radius: 8px;
+ margin-left: 4px;
+}
+
+.friends-search {
+ padding: 16px;
+}
+
+.friends-search input {
+ width: 100%;
+ padding: 10px 12px;
+ background: #0f0f0f;
+ border: none;
+ border-radius: 4px;
+ color: #e0e0e0;
+ font-family: inherit;
+}
+
+.friends-count {
+ padding: 0 16px 8px;
+ font-size: 0.75em;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ color: #666;
+}
+
+.friends-list {
+ flex: 1;
+ overflow-y: auto;
+ padding: 0 8px;
+}
+
+.friend-item {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 12px;
+ border-radius: 4px;
+ cursor: pointer;
+ border-top: 1px solid #2f3136;
+}
+
+.friend-item:hover {
+ background: #1a1a1a;
+}
+
+.friend-avatar {
+ width: 36px;
+ height: 36px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: 700;
+ position: relative;
+}
+
+.friend-status {
+ position: absolute;
+ bottom: -2px;
+ right: -2px;
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ border: 3px solid #0a0a0a;
+}
+
+.friend-info {
+ flex: 1;
+}
+
+.friend-name {
+ font-weight: 500;
+}
+
+.friend-activity,
+.friend-tag {
+ font-size: 0.8em;
+ color: #666;
+}
+
+.friend-actions {
+ display: flex;
+ gap: 8px;
+}
+
+.friend-action {
+ width: 36px;
+ height: 36px;
+ border: none;
+ background: #2f3136;
+ border-radius: 50%;
+ cursor: pointer;
+ font-size: 1em;
+}
+
+.friend-action:hover {
+ background: #40444b;
+}
+
+.friend-action.accept {
+ background: #3ba55d;
+}
+
+.friend-action.decline {
+ background: #ed4245;
+}
+
+/* Add Friend Panel */
+.add-friend-panel {
+ padding: 32px;
+}
+
+.add-friend-panel h2 {
+ margin-bottom: 8px;
+}
+
+.add-friend-panel p {
+ color: #666;
+ margin-bottom: 24px;
+}
+
+.add-friend-input {
+ display: flex;
+ gap: 12px;
+}
+
+.friend-input {
+ flex: 1;
+ padding: 12px 16px;
+ background: #0f0f0f;
+ border: 1px solid #2f3136;
+ border-radius: 4px;
+ color: #e0e0e0;
+ font-family: inherit;
+}
+
+.send-request-btn {
+ padding: 12px 24px;
+ background: #5865f2;
+ border: none;
+ border-radius: 4px;
+ color: white;
+ font-family: inherit;
+ cursor: pointer;
+}
+
+.send-request-btn:hover {
+ background: #4752c4;
+}
+
+/* Activity Panel */
+.activity-panel {
+ width: 340px;
+ background: #0f0f0f;
+ border-left: 1px solid #2f3136;
+ padding: 16px;
+}
+
+.activity-panel h3 {
+ margin-bottom: 16px;
+}
+
+.activity-placeholder {
+ text-align: center;
+ padding: 40px 20px;
+ color: #666;
+}
+
+.activity-hint {
+ font-size: 0.85em;
+ margin-top: 8px;
+}
+
+/* ==================== USER SETTINGS MODAL ==================== */
+.user-settings-modal {
+ width: 90%;
+ max-width: 1000px;
+ height: 85vh;
+ background: #18191c;
+ border-radius: 8px;
+ display: flex;
+ overflow: hidden;
+}
+
+.settings-section-title {
+ font-size: 0.7em;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ color: #666;
+ padding: 8px 12px;
+ font-weight: 700;
+}
+
+.account-card {
+ background: #0f0f0f;
+ border-radius: 8px;
+ overflow: hidden;
+ margin-bottom: 24px;
+}
+
+.account-banner {
+ height: 100px;
+}
+
+.account-info {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ padding: 16px;
+ margin-top: -40px;
+}
+
+.account-avatar {
+ width: 80px;
+ height: 80px;
+ border-radius: 50%;
+ border: 6px solid #0f0f0f;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 2em;
+ font-weight: 700;
+}
+
+.account-details {
+ flex: 1;
+}
+
+.account-details h3 {
+ font-size: 1.3em;
+}
+
+.account-tag {
+ color: #666;
+}
+
+.edit-profile-btn {
+ padding: 8px 16px;
+ background: #5865f2;
+ border: none;
+ border-radius: 4px;
+ color: white;
+ font-family: inherit;
+ cursor: pointer;
+}
+
+.account-fields {
+ background: #0f0f0f;
+ border-radius: 8px;
+ padding: 16px;
+ margin-bottom: 24px;
+}
+
+.account-field {
+ padding: 12px 0;
+ border-bottom: 1px solid #2f3136;
+}
+
+.account-field:last-child {
+ border-bottom: none;
+}
+
+.account-field label {
+ display: block;
+ font-size: 0.7em;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ color: #666;
+ margin-bottom: 4px;
+}
+
+.field-value {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.field-edit {
+ background: none;
+ border: none;
+ color: #5865f2;
+ cursor: pointer;
+ font-family: inherit;
+}
+
+.two-factor {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ padding: 16px;
+ background: #0f0f0f;
+ border-radius: 8px;
+ margin-top: 12px;
+}
+
+.two-factor-info {
+ flex: 1;
+}
+
+.two-factor-info h5 {
+ margin-bottom: 4px;
+}
+
+.two-factor-info p {
+ font-size: 0.85em;
+ color: #666;
+}
+
+.settings-btn {
+ padding: 8px 16px;
+ background: #2f3136;
+ border: none;
+ border-radius: 4px;
+ color: #e0e0e0;
+ font-family: inherit;
+ cursor: pointer;
+}
+
+.settings-btn:hover {
+ background: #40444b;
+}
+
+.settings-btn.primary {
+ background: #5865f2;
+}
+
+.settings-btn.primary:hover {
+ background: #4752c4;
+}
+
+.danger-zone h4 {
+ color: #ed4245;
+}
+
+.danger-buttons {
+ display: flex;
+ gap: 12px;
+ margin-top: 12px;
+}
+
+.settings-btn.danger-outline {
+ background: transparent;
+ border: 1px solid #ed4245;
+ color: #ed4245;
+}
+
+.settings-btn.danger {
+ background: #ed4245;
+ color: white;
+}
+
+/* Theme options */
+.theme-options {
+ display: flex;
+ gap: 12px;
+}
+
+.theme-option {
+ cursor: pointer;
+}
+
+.theme-preview {
+ display: block;
+ padding: 12px 24px;
+ border-radius: 4px;
+ border: 2px solid transparent;
+}
+
+.theme-preview.dark {
+ background: #0a0a0a;
+ color: #e0e0e0;
+}
+
+.theme-preview.light {
+ background: #ffffff;
+ color: #000;
+}
+
+.theme-option input:checked + .theme-preview {
+ border-color: #5865f2;
+}
+
+.font-slider,
+.volume-slider {
+ width: 100%;
+ margin-top: 8px;
+}
+
+.toggle-setting {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 12px 0;
+ border-bottom: 1px solid #2f3136;
+}
+
+.keybinds-header {
+ margin-bottom: 16px;
+}
+
+.keybind-list {
+ background: #0f0f0f;
+ border-radius: 8px;
+}
+
+.keybind-item {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ padding: 12px 16px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.keybind-item:last-child {
+ border-bottom: none;
+}
+
+.keybind-action {
+ flex: 1;
+}
+
+.keybind-key {
+ padding: 4px 12px;
+ background: #2f3136;
+ border-radius: 4px;
+ font-size: 0.85em;
+}
+
+.keybind-edit,
+.keybind-delete {
+ background: none;
+ border: none;
+ cursor: pointer;
+ font-size: 1em;
+}
+
+/* ==================== CONTEXT MENU ==================== */
+.context-menu {
+ background: #18191c;
+ border-radius: 4px;
+ padding: 8px 0;
+ min-width: 188px;
+ box-shadow: 0 4px 20px rgba(0,0,0,0.5);
+}
+
+.context-menu-item {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 8px 12px;
+ cursor: pointer;
+ font-size: 0.9em;
+}
+
+.context-menu-item:hover {
+ background: #5865f2;
+ color: white;
+}
+
+.context-menu-item.danger:hover {
+ background: #ed4245;
+}
+
+.context-menu-icon {
+ width: 18px;
+ text-align: center;
+}
+
+.context-menu-label {
+ flex: 1;
+}
+
+.context-menu-arrow {
+ font-size: 0.7em;
+ color: #666;
+}
+
+.context-menu-divider {
+ height: 1px;
+ background: #2f3136;
+ margin: 4px 8px;
+}
+
+/* ==================== PINNED MESSAGES ==================== */
+.pinned-panel {
+ position: fixed;
+ top: 50px;
+ right: 50px;
+ width: 420px;
+ max-height: 80vh;
+ background: #18191c;
+ border-radius: 8px;
+ box-shadow: 0 8px 40px rgba(0,0,0,0.5);
+ display: flex;
+ flex-direction: column;
+ z-index: 1000;
+}
+
+.pinned-header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 16px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.pinned-icon {
+ font-size: 1.2em;
+}
+
+.pinned-title {
+ font-weight: 600;
+ flex: 1;
+}
+
+.pinned-channel {
+ font-size: 0.85em;
+ color: #666;
+}
+
+.pinned-close {
+ background: none;
+ border: none;
+ color: #666;
+ cursor: pointer;
+ font-size: 1.2em;
+}
+
+.pinned-content {
+ flex: 1;
+ overflow-y: auto;
+ padding: 8px;
+}
+
+.pinned-empty {
+ text-align: center;
+ padding: 40px;
+ color: #666;
+}
+
+.empty-icon {
+ font-size: 3em;
+ display: block;
+ margin-bottom: 12px;
+}
+
+.pinned-list {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.pinned-message {
+ background: #0f0f0f;
+ border-radius: 4px;
+ padding: 12px;
+}
+
+.pinned-message-header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 8px;
+}
+
+.pinned-avatar {
+ width: 24px;
+ height: 24px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.7em;
+ font-weight: 700;
+}
+
+.pinned-author-info {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.pinned-author {
+ font-weight: 600;
+ font-size: 0.9em;
+}
+
+.pinned-badge {
+ font-size: 0.7em;
+ padding: 2px 6px;
+ border-radius: 3px;
+}
+
+.pinned-badge.founder,
+.pinned-badge.foundation {
+ background: rgba(255, 0, 0, 0.2);
+ color: #ff0000;
+}
+
+.pinned-badge.labs {
+ background: rgba(255, 165, 0, 0.2);
+ color: #ffa500;
+}
+
+.pinned-time {
+ font-size: 0.75em;
+ color: #666;
+}
+
+.pinned-message-text {
+ font-size: 0.9em;
+ line-height: 1.4;
+ margin-bottom: 8px;
+}
+
+.pinned-message-footer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.pinned-by {
+ font-size: 0.75em;
+ color: #666;
+}
+
+.jump-to-btn {
+ padding: 4px 12px;
+ background: #5865f2;
+ border: none;
+ border-radius: 3px;
+ color: white;
+ font-size: 0.8em;
+ cursor: pointer;
+}
+
+.pinned-footer {
+ padding: 12px 16px;
+ border-top: 1px solid #2f3136;
+}
+
+.protip {
+ font-size: 0.8em;
+ color: #666;
+}
+
+/* ==================== FILE UPLOAD ==================== */
+.file-upload-modal {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 500px;
+ background: #18191c;
+ border-radius: 8px;
+ box-shadow: 0 8px 40px rgba(0,0,0,0.5);
+ z-index: 1000;
+}
+
+.file-upload-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.upload-close {
+ background: none;
+ border: none;
+ color: #666;
+ cursor: pointer;
+ font-size: 1.2em;
+}
+
+.file-drop-zone {
+ margin: 16px;
+ padding: 40px;
+ border: 2px dashed #2f3136;
+ border-radius: 8px;
+ text-align: center;
+ cursor: pointer;
+ transition: border-color 0.2s, background 0.2s;
+}
+
+.file-drop-zone:hover,
+.file-drop-zone.dragging {
+ border-color: #5865f2;
+ background: rgba(88, 101, 242, 0.1);
+}
+
+.drop-zone-content {
+ color: #666;
+}
+
+.drop-icon {
+ font-size: 3em;
+ display: block;
+ margin-bottom: 12px;
+}
+
+.drop-hint {
+ font-size: 0.8em;
+ display: block;
+ margin-top: 8px;
+}
+
+.file-list {
+ max-height: 200px;
+ overflow-y: auto;
+ padding: 0 16px;
+}
+
+.file-item {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 12px;
+ background: #0f0f0f;
+ border-radius: 4px;
+ margin-bottom: 8px;
+}
+
+.file-preview {
+ width: 48px;
+ height: 48px;
+ object-fit: cover;
+ border-radius: 4px;
+}
+
+.file-icon {
+ width: 48px;
+ height: 48px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 2em;
+ background: #2f3136;
+ border-radius: 4px;
+}
+
+.file-info {
+ flex: 1;
+}
+
+.file-name {
+ font-size: 0.9em;
+ word-break: break-all;
+}
+
+.file-size {
+ font-size: 0.75em;
+ color: #666;
+}
+
+.file-remove {
+ background: none;
+ border: none;
+ color: #ed4245;
+ cursor: pointer;
+ font-size: 1em;
+}
+
+.upload-options {
+ padding: 0 16px;
+}
+
+.spoiler-option {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 0.9em;
+ cursor: pointer;
+}
+
+.upload-comment {
+ padding: 16px;
+}
+
+.upload-comment textarea {
+ width: 100%;
+ padding: 12px;
+ background: #0f0f0f;
+ border: none;
+ border-radius: 4px;
+ color: #e0e0e0;
+ font-family: inherit;
+ resize: none;
+}
+
+.upload-warning {
+ padding: 8px 16px;
+ color: #faa61a;
+ font-size: 0.85em;
+}
+
+.upload-footer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px;
+ border-top: 1px solid #2f3136;
+}
+
+.upload-summary {
+ font-size: 0.85em;
+ color: #666;
+}
+
+.upload-actions {
+ display: flex;
+ gap: 12px;
+}
+
+.upload-cancel {
+ padding: 8px 16px;
+ background: none;
+ border: none;
+ color: #e0e0e0;
+ cursor: pointer;
+ font-family: inherit;
+}
+
+.upload-submit {
+ padding: 8px 24px;
+ background: #5865f2;
+ border: none;
+ border-radius: 4px;
+ color: white;
+ cursor: pointer;
+ font-family: inherit;
+}
+
+.upload-submit:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+/* ==================== CREATE CHANNEL MODAL ==================== */
+.create-channel-modal {
+ width: 460px;
+ background: #18191c;
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.modal-header {
+ padding: 16px;
+ border-bottom: 1px solid #2f3136;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.modal-header h2 {
+ flex: 1;
+}
+
+.category-name {
+ font-size: 0.85em;
+ color: #666;
+}
+
+.modal-close {
+ background: none;
+ border: none;
+ color: #666;
+ cursor: pointer;
+ font-size: 1.2em;
+}
+
+.modal-content {
+ padding: 16px;
+ max-height: 60vh;
+ overflow-y: auto;
+}
+
+.section-label {
+ display: block;
+ font-size: 0.75em;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ color: #b9bbbe;
+ font-weight: 700;
+ margin-bottom: 8px;
+}
+
+.channel-types {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ margin-bottom: 20px;
+}
+
+.channel-type-option {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 12px;
+ background: #0f0f0f;
+ border-radius: 4px;
+ cursor: pointer;
+ border: 2px solid transparent;
+}
+
+.channel-type-option:hover {
+ background: #1a1a1a;
+}
+
+.channel-type-option.selected {
+ border-color: #5865f2;
+}
+
+.channel-type-option input {
+ display: none;
+}
+
+.type-icon {
+ font-size: 1.5em;
+ width: 32px;
+ text-align: center;
+}
+
+.type-info {
+ flex: 1;
+}
+
+.type-label {
+ display: block;
+ font-weight: 500;
+}
+
+.type-description {
+ font-size: 0.8em;
+ color: #666;
+}
+
+.channel-name-section {
+ margin-bottom: 20px;
+}
+
+.channel-name-input {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 12px;
+ background: #0f0f0f;
+ border-radius: 4px;
+}
+
+.channel-prefix {
+ font-size: 1.2em;
+ color: #666;
+}
+
+.channel-name-input input {
+ flex: 1;
+ background: none;
+ border: none;
+ color: #e0e0e0;
+ font-family: inherit;
+ font-size: 1em;
+}
+
+.privacy-toggle {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 12px;
+ background: #0f0f0f;
+ border-radius: 4px;
+ cursor: pointer;
+}
+
+.privacy-info {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.privacy-icon {
+ font-size: 1.5em;
+}
+
+.privacy-label {
+ display: block;
+ font-weight: 500;
+}
+
+.privacy-description {
+ font-size: 0.8em;
+ color: #666;
+}
+
+.modal-footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: 12px;
+ padding: 16px;
+ background: #0f0f0f;
+}
+
+.modal-cancel {
+ padding: 10px 20px;
+ background: none;
+ border: none;
+ color: #e0e0e0;
+ cursor: pointer;
+ font-family: inherit;
+}
+
+.modal-submit {
+ padding: 10px 24px;
+ background: #5865f2;
+ border: none;
+ border-radius: 4px;
+ color: white;
+ cursor: pointer;
+ font-family: inherit;
+}
+
+.modal-submit:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+/* ==================== INVITE MODAL ==================== */
+.invite-modal {
+ width: 440px;
+ background: #18191c;
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.invite-header {
+ padding: 16px;
+ border-bottom: 1px solid #2f3136;
+ position: relative;
+}
+
+.invite-header h2 {
+ font-size: 1.1em;
+}
+
+.invite-channel {
+ font-size: 0.85em;
+ color: #666;
+}
+
+.invite-content {
+ padding: 16px;
+}
+
+.invite-search {
+ margin-bottom: 16px;
+}
+
+.friend-search-input {
+ width: 100%;
+ padding: 10px 12px;
+ background: #0f0f0f;
+ border: none;
+ border-radius: 4px;
+ color: #e0e0e0;
+ font-family: inherit;
+}
+
+.invite-friends-list {
+ max-height: 200px;
+ overflow-y: auto;
+ margin-bottom: 16px;
+}
+
+.invite-friend-item {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 8px;
+ border-radius: 4px;
+}
+
+.invite-friend-item:hover {
+ background: #2f3136;
+}
+
+.invite-friend-avatar {
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: 700;
+}
+
+.invite-friend-name {
+ flex: 1;
+}
+
+.invite-btn {
+ padding: 6px 16px;
+ background: #3ba55d;
+ border: none;
+ border-radius: 4px;
+ color: white;
+ cursor: pointer;
+ font-family: inherit;
+ font-size: 0.85em;
+}
+
+.invite-link-section {
+ padding-top: 12px;
+ border-top: 1px solid #2f3136;
+}
+
+.invite-link-section label {
+ display: block;
+ font-size: 0.7em;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ color: #666;
+ margin-bottom: 8px;
+}
+
+.invite-link-row {
+ display: flex;
+ gap: 8px;
+}
+
+.invite-link-input {
+ flex: 1;
+ padding: 10px 12px;
+ background: #0f0f0f;
+ border: none;
+ border-radius: 4px;
+ color: #e0e0e0;
+ font-family: inherit;
+}
+
+.copy-link-btn {
+ padding: 10px 20px;
+ background: #5865f2;
+ border: none;
+ border-radius: 4px;
+ color: white;
+ cursor: pointer;
+ font-family: inherit;
+}
+
+.copy-link-btn.copied {
+ background: #3ba55d;
+}
+
+.link-expires {
+ font-size: 0.8em;
+ color: #666;
+ margin-top: 8px;
+}
+
+.edit-link-btn {
+ background: none;
+ border: none;
+ color: #5865f2;
+ cursor: pointer;
+ font-family: inherit;
+}
+
+.invite-advanced {
+ margin-top: 16px;
+ padding-top: 16px;
+ border-top: 1px solid #2f3136;
+}
+
+.advanced-option {
+ margin-bottom: 12px;
+}
+
+.advanced-option label {
+ display: block;
+ font-size: 0.7em;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ color: #666;
+ margin-bottom: 4px;
+}
+
+.advanced-select {
+ width: 100%;
+ padding: 10px 12px;
+ background: #0f0f0f;
+ border: none;
+ border-radius: 4px;
+ color: #e0e0e0;
+ font-family: inherit;
+}
+
+.temporary-toggle {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin: 12px 0;
+ font-size: 0.9em;
+}
+
+.generate-new-btn {
+ width: 100%;
+ padding: 10px;
+ background: #2f3136;
+ border: none;
+ border-radius: 4px;
+ color: #e0e0e0;
+ cursor: pointer;
+ font-family: inherit;
+}
+
+.invite-footer {
+ padding: 12px 16px;
+ background: #0f0f0f;
+}
+
+.invite-tip {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 0.85em;
+ color: #666;
+}
+
+/* ==================== NOTIFICATIONS PANEL ==================== */
+.notifications-panel {
+ position: fixed;
+ top: 0;
+ right: 0;
+ width: 480px;
+ height: 100vh;
+ background: #18191c;
+ border-left: 1px solid #2f3136;
+ display: flex;
+ flex-direction: column;
+ z-index: 100;
+}
+
+.notifications-header {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ padding: 16px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.notifications-header h3 {
+ flex: 1;
+}
+
+.notifications-actions {
+ display: flex;
+ gap: 8px;
+}
+
+.mark-read-btn,
+.clear-btn {
+ padding: 6px 12px;
+ background: #2f3136;
+ border: none;
+ border-radius: 4px;
+ color: #b9bbbe;
+ cursor: pointer;
+ font-family: inherit;
+ font-size: 0.8em;
+}
+
+.notifications-close {
+ background: none;
+ border: none;
+ color: #666;
+ cursor: pointer;
+ font-size: 1.2em;
+}
+
+.notifications-filters {
+ display: flex;
+ gap: 4px;
+ padding: 12px 16px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.notifications-list {
+ flex: 1;
+ overflow-y: auto;
+}
+
+.notifications-empty {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ color: #666;
+}
+
+.notification-item {
+ display: flex;
+ gap: 12px;
+ padding: 16px;
+ cursor: pointer;
+ border-bottom: 1px solid #2f3136;
+ position: relative;
+}
+
+.notification-item:hover {
+ background: #1a1a1a;
+}
+
+.notification-item.unread {
+ background: rgba(88, 101, 242, 0.1);
+}
+
+.notification-icon-container {
+ position: relative;
+}
+
+.notification-avatar,
+.notification-server-icon {
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: 700;
+}
+
+.notification-type-badge {
+ position: absolute;
+ bottom: -4px;
+ right: -4px;
+ width: 20px;
+ height: 20px;
+ background: #18191c;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.7em;
+}
+
+.notification-content {
+ flex: 1;
+ min-width: 0;
+}
+
+.notification-header-row {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ margin-bottom: 4px;
+ font-size: 0.85em;
+}
+
+.notification-server {
+ font-weight: 600;
+}
+
+.notification-separator {
+ color: #666;
+}
+
+.notification-channel {
+ color: #666;
+}
+
+.notification-author {
+ font-weight: 600;
+}
+
+.notification-message {
+ font-size: 0.9em;
+ color: #b9bbbe;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.notification-time {
+ font-size: 0.75em;
+ color: #666;
+ margin-top: 4px;
+}
+
+.unread-dot {
+ position: absolute;
+ top: 16px;
+ right: 16px;
+ width: 8px;
+ height: 8px;
+ background: #5865f2;
+ border-radius: 50%;
+}
+
+/* ==================== MARKDOWN STYLES ==================== */
+.code-block {
+ background: #0f0f0f;
+ border-radius: 4px;
+ padding: 12px;
+ margin: 8px 0;
+ overflow-x: auto;
+ font-family: 'Roboto Mono', monospace;
+ font-size: 0.85em;
+}
+
+.inline-code {
+ background: #0f0f0f;
+ padding: 2px 6px;
+ border-radius: 3px;
+ font-family: 'Roboto Mono', monospace;
+ font-size: 0.9em;
+}
+
+.spoiler {
+ background: #1a1a1a;
+ color: transparent;
+ border-radius: 3px;
+ padding: 0 4px;
+ cursor: pointer;
+ transition: color 0.2s;
+}
+
+.spoiler.revealed,
+.spoiler:hover {
+ color: inherit;
+}
+
+.mention {
+ background: rgba(88, 101, 242, 0.3);
+ color: #5865f2;
+ padding: 0 4px;
+ border-radius: 3px;
+ cursor: pointer;
+}
+
+.mention:hover {
+ background: #5865f2;
+ color: white;
+}
+
+.channel-mention {
+ background: rgba(88, 101, 242, 0.3);
+ color: #5865f2;
+ padding: 0 4px;
+ border-radius: 3px;
+ cursor: pointer;
+}
+
+.role-mention {
+ padding: 0 4px;
+ border-radius: 3px;
+}
+
+.message-link {
+ color: #5865f2;
+ text-decoration: none;
+}
+
+.message-link:hover {
+ text-decoration: underline;
+}
+
+.block-quote {
+ border-left: 4px solid #4f545c;
+ padding-left: 12px;
+ margin: 8px 0;
+ color: #b9bbbe;
+}
+
+/* ==================== REPLY PREVIEW ==================== */
+.reply-preview {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 16px;
+ background: #0f0f0f;
+ border-radius: 4px 4px 0 0;
+}
+
+.reply-line {
+ width: 4px;
+ height: 20px;
+ background: #5865f2;
+ border-radius: 2px;
+}
+
+.reply-content {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 0.85em;
+ min-width: 0;
+}
+
+.replying-to {
+ color: #666;
+}
+
+.reply-author {
+ font-weight: 600;
+}
+
+.reply-text {
+ color: #b9bbbe;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.reply-cancel {
+ background: none;
+ border: none;
+ color: #666;
+ cursor: pointer;
+}
+
+.message-reply-context {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ margin-bottom: 4px;
+ padding-left: 48px;
+ font-size: 0.85em;
+}
+
+.reply-connector {
+ display: flex;
+ align-items: flex-end;
+ height: 16px;
+ width: 32px;
+}
+
+.reply-line-vertical {
+ width: 2px;
+ height: 100%;
+ background: #4f545c;
+ margin-left: 16px;
+}
+
+.reply-line-horizontal {
+ width: 16px;
+ height: 2px;
+ background: #4f545c;
+}
+
+.reply-avatar-mini {
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.6em;
+ font-weight: 700;
+}
+
+.reply-author-mini {
+ font-weight: 600;
+ cursor: pointer;
+}
+
+.reply-author-mini:hover {
+ text-decoration: underline;
+}
+
+.reply-text-mini {
+ color: #b9bbbe;
+ cursor: pointer;
+}
+
+.reply-text-mini:hover {
+ color: #e0e0e0;
+}
+
+/* ==================== VIDEO CALL ==================== */
+.video-call-container {
+ position: fixed;
+ inset: 0;
+ background: #0a0a0a;
+ z-index: 1000;
+ display: flex;
+ flex-direction: column;
+}
+
+.video-call-container.fullscreen {
+ z-index: 10000;
+}
+
+.video-main {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ padding: 16px;
+ gap: 16px;
+ overflow: hidden;
+}
+
+.video-focused {
+ flex: 1;
+ background: #18191c;
+ border-radius: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ overflow: hidden;
+}
+
+.screen-share-view {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+}
+
+.screen-content {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #0f0f0f;
+}
+
+.screen-placeholder {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 16px;
+ color: #666;
+}
+
+.screen-icon {
+ font-size: 4em;
+}
+
+.streamer-pip {
+ position: absolute;
+ bottom: 16px;
+ right: 16px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 12px;
+ background: rgba(0,0,0,0.7);
+ border-radius: 4px;
+}
+
+.pip-avatar {
+ width: 24px;
+ height: 24px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.7em;
+ font-weight: 700;
+}
+
+.pip-name {
+ font-size: 0.85em;
+}
+
+.video-feed,
+.video-audio-only {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 16px;
+}
+
+.video-placeholder {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.video-avatar-large {
+ width: 150px;
+ height: 150px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 4em;
+ font-weight: 700;
+}
+
+.video-name-overlay {
+ position: absolute;
+ bottom: 16px;
+ left: 16px;
+ padding: 8px 16px;
+ background: rgba(0,0,0,0.7);
+ border-radius: 4px;
+}
+
+.audio-avatar {
+ width: 120px;
+ height: 120px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 3em;
+ font-weight: 700;
+ transition: box-shadow 0.2s;
+}
+
+.audio-avatar.speaking {
+ box-shadow: 0 0 0 4px #3ba55d;
+}
+
+.audio-name {
+ font-size: 1.2em;
+ font-weight: 600;
+}
+
+.audio-muted {
+ position: absolute;
+ bottom: 16px;
+ left: 50%;
+ transform: translateX(-50%);
+ padding: 4px 8px;
+ background: rgba(0,0,0,0.7);
+ border-radius: 4px;
+}
+
+.video-grid {
+ display: flex;
+ gap: 8px;
+ justify-content: center;
+ flex-wrap: wrap;
+ max-height: 200px;
+}
+
+.video-tile {
+ width: 180px;
+ height: 120px;
+ background: #18191c;
+ border-radius: 8px;
+ overflow: hidden;
+ cursor: pointer;
+ position: relative;
+ transition: transform 0.2s;
+}
+
+.video-tile:hover {
+ transform: scale(1.05);
+}
+
+.video-tile.speaking {
+ box-shadow: 0 0 0 2px #3ba55d;
+}
+
+.tile-video,
+.tile-audio {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.tile-avatar {
+ width: 48px;
+ height: 48px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: 700;
+}
+
+.tile-info {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ padding: 8px;
+ background: linear-gradient(transparent, rgba(0,0,0,0.7));
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.tile-name {
+ font-size: 0.8em;
+}
+
+.tile-icons {
+ display: flex;
+ gap: 4px;
+ font-size: 0.8em;
+}
+
+.video-controls {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px 24px;
+ background: #18191c;
+}
+
+.controls-left,
+.controls-right {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.controls-center {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.call-info {
+ display: flex;
+ flex-direction: column;
+}
+
+.call-channel {
+ font-weight: 600;
+ color: #3ba55d;
+}
+
+.call-duration {
+ font-size: 0.8em;
+ color: #666;
+}
+
+.control-btn {
+ width: 48px;
+ height: 48px;
+ border: none;
+ background: #2f3136;
+ border-radius: 50%;
+ cursor: pointer;
+ font-size: 1.2em;
+ transition: background 0.2s;
+}
+
+.control-btn:hover {
+ background: #40444b;
+}
+
+.control-btn.active {
+ background: #3ba55d;
+}
+
+.control-btn.active.sharing {
+ background: #5865f2;
+}
+
+.control-btn.leave {
+ background: #ed4245;
+}
+
+.control-btn.leave:hover {
+ background: #c53030;
+}
+
+/* ==================== ACTIVITY STATUS ==================== */
+.activity-status {
+ background: #0f0f0f;
+ border-radius: 8px;
+ padding: 12px;
+}
+
+.activity-header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 12px;
+ font-size: 0.75em;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ color: #b9bbbe;
+}
+
+.activity-type-icon {
+ font-size: 1.2em;
+}
+
+.activity-content {
+ display: flex;
+ gap: 12px;
+}
+
+.activity-image {
+ position: relative;
+}
+
+.activity-image img {
+ width: 60px;
+ height: 60px;
+ border-radius: 8px;
+}
+
+.activity-image-small {
+ position: absolute;
+ bottom: -4px;
+ right: -4px;
+ width: 24px;
+ height: 24px;
+ border-radius: 50%;
+ border: 3px solid #18191c;
+}
+
+.activity-details {
+ flex: 1;
+}
+
+.activity-name {
+ font-weight: 600;
+ margin-bottom: 2px;
+}
+
+.activity-detail,
+.activity-state,
+.activity-time {
+ font-size: 0.85em;
+ color: #b9bbbe;
+}
+
+.activity-buttons {
+ display: flex;
+ gap: 8px;
+ margin-top: 12px;
+}
+
+.activity-btn {
+ flex: 1;
+ padding: 8px;
+ background: #2f3136;
+ border: none;
+ border-radius: 4px;
+ color: #e0e0e0;
+ cursor: pointer;
+ font-family: inherit;
+ font-size: 0.85em;
+}
+
+.activity-btn:hover {
+ background: #40444b;
+}
+
+.activity-status-small {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ font-size: 0.8em;
+ color: #b9bbbe;
+}
+
+.activity-status-mini {
+ font-size: 0.75em;
+ color: #666;
+}
+
+/* Rich Presence */
+.rich-presence,
+.spotify-activity {
+ background: #0f0f0f;
+ border-radius: 8px;
+ padding: 12px;
+ margin-top: 8px;
+}
+
+.rp-header,
+.spotify-header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 12px;
+ font-size: 0.75em;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ color: #b9bbbe;
+}
+
+.spotify-header {
+ color: #1db954;
+}
+
+.rp-content,
+.spotify-content {
+ display: flex;
+ gap: 12px;
+}
+
+.rp-image,
+.spotify-album {
+ width: 60px;
+ height: 60px;
+ border-radius: 8px;
+ overflow: hidden;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #2f3136;
+}
+
+.rp-image img,
+.spotify-album img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.rp-placeholder,
+.spotify-placeholder {
+ font-size: 2em;
+}
+
+.rp-info,
+.spotify-info {
+ flex: 1;
+}
+
+.rp-name,
+.spotify-song {
+ font-weight: 600;
+ margin-bottom: 2px;
+}
+
+.rp-details,
+.rp-state,
+.rp-elapsed,
+.spotify-artist,
+.spotify-album-name {
+ font-size: 0.85em;
+ color: #b9bbbe;
+}
+
+.spotify-progress {
+ margin-top: 12px;
+}
+
+.progress-bar {
+ height: 4px;
+ background: #4f545c;
+ border-radius: 2px;
+ overflow: hidden;
+}
+
+.progress-fill {
+ height: 100%;
+ background: #1db954;
+ transition: width 1s linear;
+}
+
+.progress-times {
+ display: flex;
+ justify-content: space-between;
+ margin-top: 4px;
+ font-size: 0.75em;
+ color: #666;
+}
+
+/* ==================== SERVER BANNER ==================== */
+.server-banner {
+ background: #0f0f0f;
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.banner-image {
+ height: 140px;
+ position: relative;
+}
+
+.banner-overlay {
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(transparent 40%, rgba(0,0,0,0.8));
+ display: flex;
+ align-items: flex-end;
+ padding: 16px;
+}
+
+.banner-title {
+ font-size: 1.4em;
+ margin-bottom: 4px;
+}
+
+.banner-description {
+ font-size: 0.85em;
+ color: #b9bbbe;
+}
+
+.server-info-bar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 12px 16px;
+ background: #18191c;
+}
+
+.server-stats {
+ display: flex;
+ gap: 16px;
+}
+
+.stat {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.stat-indicator {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+}
+
+.stat-indicator.online {
+ background: #3ba55d;
+}
+
+.stat-value {
+ font-weight: 600;
+}
+
+.stat-label {
+ color: #666;
+ font-size: 0.85em;
+}
+
+.boost-status {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 6px 12px;
+ background: linear-gradient(135deg, rgba(255,115,250,0.2), rgba(85,62,223,0.2));
+ border-radius: 4px;
+}
+
+.boost-icon {
+ color: #ff73fa;
+}
+
+.boost-level {
+ font-weight: 600;
+ color: #ff73fa;
+}
+
+.boost-count {
+ color: #b9bbbe;
+ font-size: 0.85em;
+}
+
+.boost-perks {
+ padding: 16px;
+ border-top: 1px solid #2f3136;
+}
+
+.perks-header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-weight: 600;
+ margin-bottom: 12px;
+}
+
+.perks-list {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 8px;
+}
+
+.perk-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 0.85em;
+}
+
+.perk-check {
+ color: #3ba55d;
+}
+
+.vanity-url {
+ display: flex;
+ justify-content: space-between;
+ padding: 12px 16px;
+ border-top: 1px solid #2f3136;
+}
+
+.vanity-label {
+ color: #666;
+}
+
+.vanity-link {
+ color: #5865f2;
+}
+
+.server-banner-mini {
+ position: relative;
+ height: 60px;
+ overflow: hidden;
+}
+
+.banner-gradient {
+ position: absolute;
+ inset: 0;
+}
+
+.server-name-mini {
+ position: absolute;
+ bottom: 8px;
+ left: 12px;
+ font-weight: 600;
+ text-shadow: 0 1px 3px rgba(0,0,0,0.5);
+}
+
+.boost-progress {
+ padding: 12px;
+ background: #0f0f0f;
+ border-radius: 8px;
+}
+
+.boost-header {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 8px;
+ font-size: 0.85em;
+}
+
+.current-level {
+ font-weight: 600;
+}
+
+.next-level {
+ color: #666;
+}
+
+.boost-bar {
+ height: 8px;
+ background: #2f3136;
+ border-radius: 4px;
+ overflow: hidden;
+ margin-bottom: 8px;
+}
+
+.boost-fill {
+ height: 100%;
+ background: linear-gradient(90deg, #ff73fa, #553edf);
+ transition: width 0.3s;
+}
+
+.boost-progress .boost-count {
+ font-size: 0.8em;
+ color: #666;
+}
+
+.boost-progress.max-level .boost-bar {
+ background: linear-gradient(90deg, #ff73fa, #553edf);
+}
+
+.boost-progress.max-level .boost-label {
+ color: #ff73fa;
+ font-weight: 600;
+}
+
+/* ==================== CATEGORY ADD BUTTON ==================== */
+.channel-category {
+ position: relative;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.category-add-btn {
+ width: 18px;
+ height: 18px;
+ border: none;
+ background: transparent;
+ color: #b9bbbe;
+ font-size: 1.2em;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 4px;
+ transition: background 0.2s, color 0.2s;
+ padding: 0;
+ line-height: 1;
+}
+
+.category-add-btn:hover {
+ background: #40444b;
+ color: #fff;
+}
+
+/* ==================== GIF PICKER ==================== */
+.gif-picker {
+ position: absolute;
+ bottom: 60px;
+ right: 80px;
+ width: 420px;
+ height: 450px;
+ background: #18191c;
+ border-radius: 8px;
+ box-shadow: 0 8px 32px rgba(0,0,0,0.5);
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ z-index: 100;
+}
+
+.gif-picker-header {
+ padding: 12px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.gif-search {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ background: #0a0a0a;
+ border-radius: 4px;
+ padding: 8px 12px;
+}
+
+.gif-search .search-icon {
+ color: #666;
+}
+
+.gif-search input {
+ flex: 1;
+ background: transparent;
+ border: none;
+ color: #e0e0e0;
+ font-size: 0.95em;
+ outline: none;
+}
+
+.gif-search .clear-search {
+ background: none;
+ border: none;
+ color: #666;
+ cursor: pointer;
+ padding: 0;
+}
+
+.gif-categories {
+ display: flex;
+ gap: 4px;
+ padding: 8px 12px;
+ border-bottom: 1px solid #2f3136;
+ overflow-x: auto;
+}
+
+.gif-category-btn {
+ padding: 6px 12px;
+ background: transparent;
+ border: none;
+ border-radius: 4px;
+ color: #b9bbbe;
+ cursor: pointer;
+ font-size: 1.1em;
+ transition: background 0.2s;
+}
+
+.gif-category-btn:hover {
+ background: #2f3136;
+}
+
+.gif-category-btn.active {
+ background: #5865f2;
+ color: #fff;
+}
+
+.gif-grid {
+ flex: 1;
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 6px;
+ padding: 12px;
+ overflow-y: auto;
+}
+
+.gif-item {
+ aspect-ratio: 1;
+ background: #0f0f0f;
+ border-radius: 6px;
+ cursor: pointer;
+ position: relative;
+ overflow: hidden;
+ transition: transform 0.2s;
+}
+
+.gif-item:hover {
+ transform: scale(1.05);
+}
+
+.gif-preview {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 2.5em;
+}
+
+.gif-overlay {
+ position: absolute;
+ top: 4px;
+ right: 4px;
+ opacity: 0;
+ transition: opacity 0.2s;
+}
+
+.gif-item:hover .gif-overlay {
+ opacity: 1;
+}
+
+.gif-favorite {
+ background: rgba(0,0,0,0.6);
+ border: none;
+ border-radius: 4px;
+ padding: 4px;
+ cursor: pointer;
+ font-size: 0.9em;
+}
+
+.gif-favorite.active {
+ color: #faa61a;
+}
+
+.gif-empty {
+ grid-column: 1 / -1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ color: #666;
+ font-size: 2em;
+}
+
+.gif-footer {
+ padding: 8px 12px;
+ border-top: 1px solid #2f3136;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 0.75em;
+}
+
+.gif-powered {
+ color: #666;
+}
+
+.gif-tenor {
+ color: #fff;
+ font-weight: 700;
+}
+
+/* ==================== STICKER PICKER ==================== */
+.sticker-picker {
+ position: absolute;
+ bottom: 60px;
+ right: 80px;
+ width: 420px;
+ height: 450px;
+ background: #18191c;
+ border-radius: 8px;
+ box-shadow: 0 8px 32px rgba(0,0,0,0.5);
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ z-index: 100;
+}
+
+.sticker-picker-header {
+ padding: 12px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.sticker-tabs {
+ display: flex;
+ padding: 0 12px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.sticker-tab {
+ padding: 12px 16px;
+ background: transparent;
+ border: none;
+ color: #b9bbbe;
+ cursor: pointer;
+ border-bottom: 2px solid transparent;
+ transition: all 0.2s;
+}
+
+.sticker-tab:hover {
+ color: #fff;
+}
+
+.sticker-tab.active {
+ color: #fff;
+ border-bottom-color: #5865f2;
+}
+
+.sticker-packs {
+ display: flex;
+ gap: 4px;
+ padding: 8px 12px;
+ border-bottom: 1px solid #2f3136;
+ overflow-x: auto;
+}
+
+.sticker-pack-btn {
+ width: 40px;
+ height: 40px;
+ background: #0f0f0f;
+ border: none;
+ border-radius: 8px;
+ cursor: pointer;
+ font-size: 1.2em;
+ transition: background 0.2s;
+}
+
+.sticker-pack-btn:hover {
+ background: #2f3136;
+}
+
+.sticker-pack-btn.active {
+ background: #5865f2;
+}
+
+.sticker-grid {
+ flex: 1;
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 8px;
+ padding: 12px;
+ overflow-y: auto;
+}
+
+.sticker-item {
+ aspect-ratio: 1;
+ background: #0f0f0f;
+ border-radius: 8px;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 2.5em;
+ transition: transform 0.2s, background 0.2s;
+}
+
+.sticker-item:hover {
+ transform: scale(1.1);
+ background: #2f3136;
+}
+
+.sticker-locked {
+ opacity: 0.5;
+ position: relative;
+}
+
+.sticker-locked::after {
+ content: '๐';
+ position: absolute;
+ bottom: 4px;
+ right: 4px;
+ font-size: 0.4em;
+}
+
+/* ==================== SOUNDBOARD ==================== */
+.soundboard {
+ position: absolute;
+ bottom: 60px;
+ left: 240px;
+ width: 380px;
+ background: #18191c;
+ border-radius: 8px;
+ box-shadow: 0 8px 32px rgba(0,0,0,0.5);
+ overflow: hidden;
+ z-index: 100;
+}
+
+.soundboard-header {
+ padding: 12px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.soundboard-search {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ background: #0a0a0a;
+ border-radius: 4px;
+ padding: 8px 12px;
+}
+
+.soundboard-search input {
+ flex: 1;
+ background: transparent;
+ border: none;
+ color: #e0e0e0;
+ outline: none;
+}
+
+.soundboard-categories {
+ display: flex;
+ gap: 4px;
+ padding: 8px 12px;
+ border-bottom: 1px solid #2f3136;
+ overflow-x: auto;
+}
+
+.soundboard-cat-btn {
+ padding: 6px 12px;
+ background: transparent;
+ border: none;
+ border-radius: 4px;
+ color: #b9bbbe;
+ cursor: pointer;
+ font-size: 0.85em;
+ white-space: nowrap;
+ transition: background 0.2s;
+}
+
+.soundboard-cat-btn:hover {
+ background: #2f3136;
+}
+
+.soundboard-cat-btn.active {
+ background: #5865f2;
+ color: #fff;
+}
+
+.soundboard-volume {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 12px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.soundboard-volume input[type="range"] {
+ flex: 1;
+ accent-color: #5865f2;
+}
+
+.sound-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 8px;
+ padding: 12px;
+ max-height: 300px;
+ overflow-y: auto;
+}
+
+.sound-btn {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 4px;
+ padding: 12px;
+ background: #0f0f0f;
+ border: none;
+ border-radius: 8px;
+ cursor: pointer;
+ transition: transform 0.2s, background 0.2s;
+}
+
+.sound-btn:hover {
+ background: #2f3136;
+ transform: scale(1.05);
+}
+
+.sound-btn.playing {
+ background: #5865f2;
+ animation: pulse 0.5s infinite;
+}
+
+@keyframes pulse {
+ 0%, 100% { transform: scale(1); }
+ 50% { transform: scale(1.05); }
+}
+
+.sound-emoji {
+ font-size: 1.5em;
+}
+
+.sound-name {
+ font-size: 0.75em;
+ color: #b9bbbe;
+}
+
+.sound-duration {
+ font-size: 0.65em;
+ color: #666;
+}
+
+.sound-favorite {
+ position: absolute;
+ top: 4px;
+ right: 4px;
+ background: none;
+ border: none;
+ cursor: pointer;
+ opacity: 0;
+ transition: opacity 0.2s;
+}
+
+.sound-btn:hover .sound-favorite {
+ opacity: 1;
+}
+
+/* ==================== POLL CREATOR ==================== */
+.poll-creator {
+ background: #18191c;
+ border-radius: 8px;
+ padding: 16px;
+ margin: 8px 0;
+}
+
+.poll-header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 16px;
+}
+
+.poll-icon {
+ font-size: 1.5em;
+}
+
+.poll-title {
+ font-weight: 600;
+}
+
+.poll-question-input {
+ width: 100%;
+ padding: 12px;
+ background: #0a0a0a;
+ border: 1px solid #2f3136;
+ border-radius: 4px;
+ color: #e0e0e0;
+ font-size: 1em;
+ margin-bottom: 12px;
+}
+
+.poll-question-input:focus {
+ outline: none;
+ border-color: #5865f2;
+}
+
+.poll-options {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ margin-bottom: 12px;
+}
+
+.poll-option-input {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.poll-option-input input {
+ flex: 1;
+ padding: 10px 12px;
+ background: #0a0a0a;
+ border: 1px solid #2f3136;
+ border-radius: 4px;
+ color: #e0e0e0;
+}
+
+.poll-option-input input:focus {
+ outline: none;
+ border-color: #5865f2;
+}
+
+.poll-option-remove {
+ width: 32px;
+ height: 32px;
+ background: transparent;
+ border: none;
+ color: #666;
+ cursor: pointer;
+ border-radius: 4px;
+}
+
+.poll-option-remove:hover {
+ background: #2f3136;
+ color: #ed4245;
+}
+
+.poll-add-option {
+ padding: 10px;
+ background: transparent;
+ border: 1px dashed #2f3136;
+ border-radius: 4px;
+ color: #b9bbbe;
+ cursor: pointer;
+ width: 100%;
+ margin-bottom: 16px;
+}
+
+.poll-add-option:hover {
+ border-color: #5865f2;
+ color: #5865f2;
+}
+
+.poll-settings {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 16px;
+ margin-bottom: 16px;
+ padding: 12px;
+ background: #0f0f0f;
+ border-radius: 4px;
+}
+
+.poll-setting {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.poll-setting label {
+ color: #b9bbbe;
+ font-size: 0.9em;
+}
+
+.poll-duration select {
+ padding: 6px 10px;
+ background: #2f3136;
+ border: none;
+ border-radius: 4px;
+ color: #e0e0e0;
+}
+
+.poll-actions {
+ display: flex;
+ justify-content: flex-end;
+ gap: 8px;
+}
+
+.poll-cancel,
+.poll-create {
+ padding: 10px 20px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-weight: 500;
+}
+
+.poll-cancel {
+ background: transparent;
+ color: #b9bbbe;
+}
+
+.poll-cancel:hover {
+ color: #fff;
+}
+
+.poll-create {
+ background: #5865f2;
+ color: #fff;
+}
+
+.poll-create:hover {
+ background: #4752c4;
+}
+
+/* ==================== QUICK SWITCHER ==================== */
+.quick-switcher-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(0,0,0,0.7);
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ padding-top: 100px;
+ z-index: 1000;
+}
+
+.quick-switcher {
+ width: 560px;
+ background: #18191c;
+ border-radius: 8px;
+ box-shadow: 0 8px 32px rgba(0,0,0,0.5);
+ overflow: hidden;
+}
+
+.quick-switcher-input {
+ width: 100%;
+ padding: 20px;
+ background: transparent;
+ border: none;
+ border-bottom: 1px solid #2f3136;
+ color: #e0e0e0;
+ font-size: 1.2em;
+ outline: none;
+}
+
+.quick-switcher-filters {
+ display: flex;
+ gap: 4px;
+ padding: 8px 12px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.filter-btn {
+ padding: 4px 10px;
+ background: transparent;
+ border: none;
+ border-radius: 4px;
+ color: #b9bbbe;
+ font-size: 0.85em;
+ cursor: pointer;
+}
+
+.filter-btn:hover {
+ background: #2f3136;
+}
+
+.filter-btn.active {
+ background: #5865f2;
+ color: #fff;
+}
+
+.quick-switcher-results {
+ max-height: 400px;
+ overflow-y: auto;
+}
+
+.qs-section-label {
+ padding: 8px 16px;
+ font-size: 0.75em;
+ font-weight: 700;
+ text-transform: uppercase;
+ color: #666;
+}
+
+.qs-item {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 10px 16px;
+ cursor: pointer;
+ transition: background 0.2s;
+}
+
+.qs-item:hover,
+.qs-item.selected {
+ background: #2f3136;
+}
+
+.qs-item-icon {
+ width: 36px;
+ height: 36px;
+ background: #0f0f0f;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: 600;
+}
+
+.qs-item-icon.channel {
+ border-radius: 4px;
+ background: transparent;
+ font-size: 1.2em;
+}
+
+.qs-item-info {
+ flex: 1;
+}
+
+.qs-item-name {
+ font-weight: 500;
+}
+
+.qs-item-detail {
+ font-size: 0.8em;
+ color: #666;
+}
+
+.qs-item-hint {
+ font-size: 0.75em;
+ color: #666;
+}
+
+.quick-switcher-footer {
+ padding: 8px 16px;
+ border-top: 1px solid #2f3136;
+ display: flex;
+ gap: 16px;
+ font-size: 0.75em;
+ color: #666;
+}
+
+.qs-hint {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.qs-key {
+ padding: 2px 6px;
+ background: #0f0f0f;
+ border-radius: 3px;
+ font-family: monospace;
+}
+
+/* ==================== NITRO PANEL ==================== */
+.nitro-panel {
+ background: linear-gradient(135deg, #5865f2 0%, #eb459e 100%);
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.nitro-header {
+ padding: 32px;
+ text-align: center;
+}
+
+.nitro-logo {
+ font-size: 3em;
+ margin-bottom: 16px;
+}
+
+.nitro-title {
+ font-size: 1.8em;
+ font-weight: 700;
+ margin-bottom: 8px;
+}
+
+.nitro-subtitle {
+ color: rgba(255,255,255,0.8);
+}
+
+.nitro-plans {
+ display: flex;
+ gap: 16px;
+ padding: 0 32px 32px;
+}
+
+.nitro-plan {
+ flex: 1;
+ background: rgba(0,0,0,0.3);
+ border-radius: 8px;
+ padding: 24px;
+ text-align: center;
+}
+
+.nitro-plan.featured {
+ background: rgba(255,255,255,0.1);
+ border: 2px solid rgba(255,255,255,0.3);
+}
+
+.plan-name {
+ font-size: 1.2em;
+ font-weight: 700;
+ margin-bottom: 8px;
+}
+
+.plan-price {
+ font-size: 2em;
+ font-weight: 700;
+ margin-bottom: 4px;
+}
+
+.plan-period {
+ font-size: 0.85em;
+ color: rgba(255,255,255,0.7);
+ margin-bottom: 16px;
+}
+
+.plan-features {
+ text-align: left;
+ margin-bottom: 24px;
+}
+
+.plan-feature {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 8px;
+ font-size: 0.9em;
+}
+
+.plan-feature .check {
+ color: #3ba55d;
+}
+
+.plan-btn {
+ width: 100%;
+ padding: 12px;
+ background: #fff;
+ border: none;
+ border-radius: 4px;
+ color: #5865f2;
+ font-weight: 600;
+ cursor: pointer;
+ transition: transform 0.2s;
+}
+
+.plan-btn:hover {
+ transform: scale(1.02);
+}
+
+.nitro-perks {
+ background: #18191c;
+ padding: 32px;
+}
+
+.perks-title {
+ font-size: 1.2em;
+ font-weight: 600;
+ margin-bottom: 16px;
+}
+
+.perk-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 16px;
+}
+
+.perk-item {
+ display: flex;
+ gap: 12px;
+ padding: 12px;
+ background: #0f0f0f;
+ border-radius: 8px;
+}
+
+.perk-icon {
+ font-size: 1.5em;
+}
+
+.perk-text h4 {
+ margin-bottom: 4px;
+}
+
+.perk-text p {
+ font-size: 0.85em;
+ color: #b9bbbe;
+}
+
+/* ==================== SERVER DISCOVERY ==================== */
+.server-discovery {
+ flex: 1;
+ background: #18191c;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+.discovery-header {
+ padding: 24px;
+ background: linear-gradient(135deg, #5865f2, #eb459e);
+}
+
+.discovery-title {
+ font-size: 1.8em;
+ font-weight: 700;
+ margin-bottom: 8px;
+}
+
+.discovery-subtitle {
+ color: rgba(255,255,255,0.8);
+ margin-bottom: 16px;
+}
+
+.discovery-search {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ background: rgba(0,0,0,0.3);
+ border-radius: 8px;
+ padding: 12px 16px;
+}
+
+.discovery-search input {
+ flex: 1;
+ background: transparent;
+ border: none;
+ color: #fff;
+ font-size: 1em;
+ outline: none;
+}
+
+.discovery-categories {
+ display: flex;
+ gap: 8px;
+ padding: 16px 24px;
+ border-bottom: 1px solid #2f3136;
+ overflow-x: auto;
+}
+
+.discovery-cat-btn {
+ padding: 8px 16px;
+ background: #2f3136;
+ border: none;
+ border-radius: 20px;
+ color: #b9bbbe;
+ cursor: pointer;
+ white-space: nowrap;
+ transition: all 0.2s;
+}
+
+.discovery-cat-btn:hover {
+ background: #40444b;
+}
+
+.discovery-cat-btn.active {
+ background: #5865f2;
+ color: #fff;
+}
+
+.discovery-content {
+ flex: 1;
+ overflow-y: auto;
+ padding: 24px;
+}
+
+.discovery-section {
+ margin-bottom: 32px;
+}
+
+.discovery-section-title {
+ font-size: 1.2em;
+ font-weight: 600;
+ margin-bottom: 16px;
+}
+
+.discovery-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+ gap: 16px;
+}
+
+.discovery-server-card {
+ background: #0f0f0f;
+ border-radius: 8px;
+ overflow: hidden;
+ cursor: pointer;
+ transition: transform 0.2s;
+}
+
+.discovery-server-card:hover {
+ transform: translateY(-4px);
+}
+
+.server-card-banner {
+ height: 100px;
+ background: linear-gradient(135deg, #5865f2, #eb459e);
+ position: relative;
+}
+
+.server-card-icon {
+ position: absolute;
+ bottom: -24px;
+ left: 16px;
+ width: 48px;
+ height: 48px;
+ background: #18191c;
+ border-radius: 16px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.5em;
+ border: 4px solid #0f0f0f;
+}
+
+.server-card-body {
+ padding: 32px 16px 16px;
+}
+
+.server-card-name {
+ font-weight: 600;
+ margin-bottom: 8px;
+}
+
+.server-card-description {
+ font-size: 0.85em;
+ color: #b9bbbe;
+ margin-bottom: 12px;
+ line-height: 1.4;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
+
+.server-card-meta {
+ display: flex;
+ gap: 16px;
+ font-size: 0.8em;
+ color: #666;
+}
+
+.server-card-members {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.server-card-members .online-dot {
+ width: 8px;
+ height: 8px;
+ background: #3ba55d;
+ border-radius: 50%;
+}
+
+/* ==================== STAGE CHANNEL ==================== */
+.stage-channel {
+ flex: 1;
+ background: #0a0a0a;
+ display: flex;
+ flex-direction: column;
+}
+
+.stage-header {
+ padding: 16px;
+ background: #18191c;
+ border-bottom: 1px solid #2f3136;
+}
+
+.stage-title {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 1.2em;
+ font-weight: 600;
+ margin-bottom: 4px;
+}
+
+.stage-topic {
+ color: #b9bbbe;
+ font-size: 0.9em;
+}
+
+.stage-main {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ padding: 24px;
+}
+
+.stage-speakers {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 24px;
+ justify-content: center;
+ margin-bottom: 32px;
+}
+
+.speaker-card {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 8px;
+}
+
+.speaker-avatar {
+ width: 80px;
+ height: 80px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 2em;
+ font-weight: 700;
+ position: relative;
+}
+
+.speaker-avatar.speaking {
+ box-shadow: 0 0 0 4px #3ba55d;
+}
+
+.speaker-muted-icon {
+ position: absolute;
+ bottom: -4px;
+ right: -4px;
+ background: #ed4245;
+ border-radius: 50%;
+ width: 24px;
+ height: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.6em;
+}
+
+.speaker-name {
+ font-weight: 500;
+}
+
+.speaker-role {
+ font-size: 0.8em;
+ color: #faa61a;
+}
+
+.stage-audience {
+ background: #18191c;
+ border-radius: 8px;
+ padding: 16px;
+}
+
+.audience-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 12px;
+}
+
+.audience-title {
+ font-weight: 600;
+}
+
+.audience-count {
+ color: #666;
+ font-size: 0.85em;
+}
+
+.audience-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+.audience-member {
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.9em;
+ font-weight: 600;
+}
+
+.stage-controls {
+ display: flex;
+ justify-content: center;
+ gap: 12px;
+ padding: 16px;
+ background: #18191c;
+}
+
+.stage-control-btn {
+ padding: 12px 24px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-weight: 500;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ transition: background 0.2s;
+}
+
+.stage-control-btn.primary {
+ background: #5865f2;
+ color: #fff;
+}
+
+.stage-control-btn.primary:hover {
+ background: #4752c4;
+}
+
+.stage-control-btn.danger {
+ background: #ed4245;
+ color: #fff;
+}
+
+.stage-control-btn.danger:hover {
+ background: #c53030;
+}
+
+.stage-control-btn.secondary {
+ background: #2f3136;
+ color: #b9bbbe;
+}
+
+.stage-control-btn.secondary:hover {
+ background: #40444b;
+}
+
+/* ==================== FORUM CHANNEL ==================== */
+.forum-channel {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ background: #18191c;
+}
+
+.forum-header {
+ padding: 16px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.forum-title {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 1.2em;
+ font-weight: 600;
+ margin-bottom: 8px;
+}
+
+.forum-description {
+ color: #b9bbbe;
+ font-size: 0.9em;
+ margin-bottom: 16px;
+}
+
+.forum-toolbar {
+ display: flex;
+ gap: 12px;
+ align-items: center;
+}
+
+.forum-search {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ background: #0a0a0a;
+ border-radius: 4px;
+ padding: 8px 12px;
+}
+
+.forum-search input {
+ flex: 1;
+ background: transparent;
+ border: none;
+ color: #e0e0e0;
+ outline: none;
+}
+
+.forum-sort {
+ padding: 8px 12px;
+ background: #2f3136;
+ border: none;
+ border-radius: 4px;
+ color: #b9bbbe;
+ cursor: pointer;
+}
+
+.forum-new-post {
+ padding: 8px 16px;
+ background: #5865f2;
+ border: none;
+ border-radius: 4px;
+ color: #fff;
+ cursor: pointer;
+ font-weight: 500;
+}
+
+.forum-new-post:hover {
+ background: #4752c4;
+}
+
+.forum-tags {
+ display: flex;
+ gap: 8px;
+ padding: 12px 16px;
+ border-bottom: 1px solid #2f3136;
+ overflow-x: auto;
+}
+
+.forum-tag {
+ padding: 4px 12px;
+ background: #2f3136;
+ border-radius: 16px;
+ font-size: 0.85em;
+ color: #b9bbbe;
+ cursor: pointer;
+ white-space: nowrap;
+}
+
+.forum-tag:hover {
+ background: #40444b;
+}
+
+.forum-tag.active {
+ background: #5865f2;
+ color: #fff;
+}
+
+.forum-posts {
+ flex: 1;
+ overflow-y: auto;
+ padding: 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.forum-post {
+ background: #0f0f0f;
+ border-radius: 8px;
+ padding: 16px;
+ cursor: pointer;
+ transition: background 0.2s;
+}
+
+.forum-post:hover {
+ background: #1a1a1a;
+}
+
+.forum-post-header {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 8px;
+}
+
+.forum-post-avatar {
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.85em;
+ font-weight: 600;
+}
+
+.forum-post-author {
+ font-weight: 500;
+}
+
+.forum-post-time {
+ color: #666;
+ font-size: 0.8em;
+}
+
+.forum-post-title {
+ font-weight: 600;
+ margin-bottom: 8px;
+}
+
+.forum-post-preview {
+ color: #b9bbbe;
+ font-size: 0.9em;
+ line-height: 1.4;
+ margin-bottom: 12px;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
+
+.forum-post-footer {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+}
+
+.forum-post-tags {
+ display: flex;
+ gap: 6px;
+ flex: 1;
+}
+
+.forum-post-tag {
+ padding: 2px 8px;
+ background: #2f3136;
+ border-radius: 4px;
+ font-size: 0.75em;
+ color: #b9bbbe;
+}
+
+.forum-post-stats {
+ display: flex;
+ gap: 12px;
+ font-size: 0.8em;
+ color: #666;
+}
+
+.forum-post-stat {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+/* ==================== EVENTS PANEL ==================== */
+.events-panel {
+ background: #18191c;
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.events-header {
+ padding: 16px;
+ border-bottom: 1px solid #2f3136;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.events-title {
+ font-weight: 600;
+}
+
+.events-create-btn {
+ padding: 6px 12px;
+ background: #5865f2;
+ border: none;
+ border-radius: 4px;
+ color: #fff;
+ cursor: pointer;
+ font-size: 0.85em;
+}
+
+.events-tabs {
+ display: flex;
+ border-bottom: 1px solid #2f3136;
+}
+
+.events-tab {
+ flex: 1;
+ padding: 12px;
+ background: transparent;
+ border: none;
+ color: #b9bbbe;
+ cursor: pointer;
+ border-bottom: 2px solid transparent;
+}
+
+.events-tab:hover {
+ color: #fff;
+}
+
+.events-tab.active {
+ color: #fff;
+ border-bottom-color: #5865f2;
+}
+
+.events-list {
+ padding: 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ max-height: 400px;
+ overflow-y: auto;
+}
+
+.event-card {
+ background: #0f0f0f;
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.event-card-image {
+ height: 80px;
+ background: linear-gradient(135deg, #5865f2, #eb459e);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 2em;
+}
+
+.event-card-body {
+ padding: 12px;
+}
+
+.event-card-date {
+ font-size: 0.75em;
+ color: #5865f2;
+ font-weight: 600;
+ margin-bottom: 4px;
+}
+
+.event-card-title {
+ font-weight: 600;
+ margin-bottom: 4px;
+}
+
+.event-card-location {
+ font-size: 0.85em;
+ color: #b9bbbe;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ margin-bottom: 8px;
+}
+
+.event-card-footer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.event-interested {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ font-size: 0.85em;
+ color: #666;
+}
+
+.event-join-btn {
+ padding: 6px 12px;
+ background: #3ba55d;
+ border: none;
+ border-radius: 4px;
+ color: #fff;
+ cursor: pointer;
+ font-size: 0.85em;
+}
+
+.event-join-btn:hover {
+ background: #2d8049;
+}
+
+.events-empty {
+ padding: 32px;
+ text-align: center;
+ color: #666;
+}
+
+.events-empty-icon {
+ font-size: 3em;
+ margin-bottom: 12px;
+}
+
+/* ==================== AUDIT LOG ==================== */
+.audit-log {
+ width: 700px;
+ max-height: 600px;
+ background: #18191c;
+ border-radius: 8px;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+}
+
+.audit-log-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px 20px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.audit-log-header h2 {
+ margin: 0;
+ font-size: 1.25em;
+}
+
+.audit-log-close {
+ width: 32px;
+ height: 32px;
+ background: transparent;
+ border: none;
+ color: #b9bbbe;
+ cursor: pointer;
+ border-radius: 4px;
+ font-size: 1.2em;
+}
+
+.audit-log-close:hover {
+ background: #2f3136;
+ color: #fff;
+}
+
+.audit-log-filters {
+ display: flex;
+ gap: 12px;
+ padding: 12px 20px;
+ border-bottom: 1px solid #2f3136;
+ background: #0f0f0f;
+}
+
+.audit-filter-group {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.audit-filter-group label {
+ font-size: 0.7em;
+ text-transform: uppercase;
+ font-weight: 600;
+ color: #666;
+}
+
+.audit-filter-group input,
+.audit-filter-group select {
+ padding: 8px 12px;
+ background: #2f3136;
+ border: none;
+ border-radius: 4px;
+ color: #e0e0e0;
+ font-size: 0.9em;
+}
+
+.audit-filter-group input:focus {
+ outline: none;
+ box-shadow: 0 0 0 2px #5865f2;
+}
+
+.audit-log-entries {
+ flex: 1;
+ overflow-y: auto;
+ padding: 8px 0;
+}
+
+.audit-entry {
+ padding: 12px 20px;
+ border-bottom: 1px solid #0f0f0f;
+ cursor: pointer;
+ transition: background 0.2s;
+}
+
+.audit-entry:hover {
+ background: #1f2023;
+}
+
+.audit-entry.expanded {
+ background: #1f2023;
+}
+
+.audit-entry-main {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.audit-action-icon {
+ width: 36px;
+ height: 36px;
+ background: #2f3136;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.1em;
+}
+
+.audit-entry-content {
+ flex: 1;
+}
+
+.audit-entry-summary {
+ color: #e0e0e0;
+ font-size: 0.95em;
+}
+
+.audit-entry-summary strong {
+ color: #fff;
+}
+
+.audit-entry-time {
+ font-size: 0.8em;
+ color: #666;
+ margin-top: 2px;
+}
+
+.audit-expand-icon {
+ color: #666;
+ font-size: 0.8em;
+ transition: transform 0.2s;
+}
+
+.audit-entry.expanded .audit-expand-icon {
+ transform: rotate(90deg);
+}
+
+.audit-entry-details {
+ margin-top: 12px;
+ padding: 12px;
+ background: #0a0a0a;
+ border-radius: 4px;
+ font-size: 0.85em;
+}
+
+.audit-detail-row {
+ display: flex;
+ gap: 8px;
+ margin-bottom: 6px;
+}
+
+.audit-detail-row:last-child {
+ margin-bottom: 0;
+}
+
+.audit-detail-label {
+ color: #666;
+ min-width: 80px;
+}
+
+.audit-detail-value {
+ color: #b9bbbe;
+}
+
+.audit-log-empty {
+ padding: 40px;
+ text-align: center;
+ color: #666;
+}
+
+/* ==================== EMOJI UPLOAD ==================== */
+.emoji-upload {
+ width: 640px;
+ background: #18191c;
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.emoji-upload-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px 20px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.emoji-upload-header h2 {
+ margin: 0;
+ font-size: 1.25em;
+}
+
+.emoji-upload-close {
+ width: 32px;
+ height: 32px;
+ background: transparent;
+ border: none;
+ color: #b9bbbe;
+ cursor: pointer;
+ border-radius: 4px;
+ font-size: 1.2em;
+}
+
+.emoji-upload-close:hover {
+ background: #2f3136;
+ color: #fff;
+}
+
+.emoji-upload-tabs {
+ display: flex;
+ border-bottom: 1px solid #2f3136;
+}
+
+.emoji-tab {
+ flex: 1;
+ padding: 12px;
+ background: transparent;
+ border: none;
+ color: #b9bbbe;
+ cursor: pointer;
+ border-bottom: 2px solid transparent;
+}
+
+.emoji-tab:hover {
+ color: #fff;
+}
+
+.emoji-tab.active {
+ color: #fff;
+ border-bottom-color: #5865f2;
+}
+
+.emoji-upload-content {
+ padding: 20px;
+}
+
+.emoji-slots-info {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 12px 16px;
+ background: #0f0f0f;
+ border-radius: 4px;
+ margin-bottom: 16px;
+}
+
+.slots-bar {
+ flex: 1;
+ height: 8px;
+ background: #2f3136;
+ border-radius: 4px;
+ margin: 0 16px;
+ overflow: hidden;
+}
+
+.slots-fill {
+ height: 100%;
+ background: #5865f2;
+ border-radius: 4px;
+ transition: width 0.3s;
+}
+
+.upload-dropzone {
+ border: 2px dashed #2f3136;
+ border-radius: 8px;
+ padding: 40px 20px;
+ text-align: center;
+ margin-bottom: 16px;
+ transition: all 0.2s;
+ cursor: pointer;
+}
+
+.upload-dropzone:hover,
+.upload-dropzone.dragover {
+ border-color: #5865f2;
+ background: rgba(88, 101, 242, 0.1);
+}
+
+.dropzone-icon {
+ font-size: 3em;
+ margin-bottom: 12px;
+}
+
+.dropzone-text {
+ color: #b9bbbe;
+ margin-bottom: 4px;
+}
+
+.dropzone-hint {
+ font-size: 0.8em;
+ color: #666;
+}
+
+.upload-preview {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ padding: 16px;
+ background: #0f0f0f;
+ border-radius: 8px;
+ margin-bottom: 16px;
+}
+
+.preview-image {
+ width: 64px;
+ height: 64px;
+ background: #2f3136;
+ border-radius: 4px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 2em;
+}
+
+.preview-info {
+ flex: 1;
+}
+
+.preview-name-input {
+ width: 100%;
+ padding: 8px 12px;
+ background: #2f3136;
+ border: none;
+ border-radius: 4px;
+ color: #e0e0e0;
+ margin-bottom: 4px;
+}
+
+.preview-name-input:focus {
+ outline: none;
+ box-shadow: 0 0 0 2px #5865f2;
+}
+
+.preview-file-info {
+ font-size: 0.8em;
+ color: #666;
+}
+
+.preview-remove {
+ width: 32px;
+ height: 32px;
+ background: transparent;
+ border: none;
+ color: #666;
+ cursor: pointer;
+ border-radius: 4px;
+}
+
+.preview-remove:hover {
+ background: #2f3136;
+ color: #ed4245;
+}
+
+.upload-actions {
+ display: flex;
+ justify-content: flex-end;
+ gap: 8px;
+}
+
+.upload-cancel,
+.upload-submit {
+ padding: 10px 20px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-weight: 500;
+}
+
+.upload-cancel {
+ background: transparent;
+ color: #b9bbbe;
+}
+
+.upload-cancel:hover {
+ color: #fff;
+}
+
+.upload-submit {
+ background: #5865f2;
+ color: #fff;
+}
+
+.upload-submit:hover {
+ background: #4752c4;
+}
+
+.upload-submit:disabled {
+ background: #4752c4;
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.existing-emojis {
+ margin-top: 20px;
+}
+
+.existing-emojis h4 {
+ margin: 0 0 12px 0;
+ font-size: 0.9em;
+ color: #b9bbbe;
+}
+
+.emoji-grid {
+ display: grid;
+ grid-template-columns: repeat(8, 1fr);
+ gap: 8px;
+}
+
+.emoji-grid-item {
+ aspect-ratio: 1;
+ background: #2f3136;
+ border-radius: 4px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.5em;
+ cursor: pointer;
+ transition: all 0.2s;
+ position: relative;
+}
+
+.emoji-grid-item:hover {
+ background: #3f4248;
+ transform: scale(1.1);
+}
+
+.emoji-edit-overlay {
+ position: absolute;
+ inset: 0;
+ background: rgba(0,0,0,0.7);
+ border-radius: 4px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ opacity: 0;
+ transition: opacity 0.2s;
+}
+
+.emoji-grid-item:hover .emoji-edit-overlay {
+ opacity: 1;
+}
+
+/* ==================== SERVER INSIGHTS ==================== */
+.server-insights {
+ width: 800px;
+ max-height: 700px;
+ background: #18191c;
+ border-radius: 8px;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+}
+
+.insights-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px 20px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.insights-header h2 {
+ margin: 0;
+ font-size: 1.25em;
+}
+
+.insights-close {
+ width: 32px;
+ height: 32px;
+ background: transparent;
+ border: none;
+ color: #b9bbbe;
+ cursor: pointer;
+ border-radius: 4px;
+ font-size: 1.2em;
+}
+
+.insights-close:hover {
+ background: #2f3136;
+ color: #fff;
+}
+
+.insights-tabs {
+ display: flex;
+ border-bottom: 1px solid #2f3136;
+}
+
+.insights-tab {
+ flex: 1;
+ padding: 12px;
+ background: transparent;
+ border: none;
+ color: #b9bbbe;
+ cursor: pointer;
+ border-bottom: 2px solid transparent;
+}
+
+.insights-tab:hover {
+ color: #fff;
+}
+
+.insights-tab.active {
+ color: #fff;
+ border-bottom-color: #5865f2;
+}
+
+.insights-time-range {
+ padding: 12px 20px;
+ background: #0f0f0f;
+ border-bottom: 1px solid #2f3136;
+ display: flex;
+ gap: 8px;
+}
+
+.time-range-btn {
+ padding: 6px 12px;
+ background: transparent;
+ border: 1px solid #2f3136;
+ border-radius: 4px;
+ color: #b9bbbe;
+ cursor: pointer;
+ font-size: 0.85em;
+}
+
+.time-range-btn:hover {
+ background: #2f3136;
+}
+
+.time-range-btn.active {
+ background: #5865f2;
+ border-color: #5865f2;
+ color: #fff;
+}
+
+.insights-content {
+ flex: 1;
+ overflow-y: auto;
+ padding: 20px;
+}
+
+.insights-stats-grid {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 16px;
+ margin-bottom: 24px;
+}
+
+.insights-stat-card {
+ background: #0f0f0f;
+ border-radius: 8px;
+ padding: 16px;
+}
+
+.stat-label {
+ font-size: 0.75em;
+ text-transform: uppercase;
+ color: #666;
+ margin-bottom: 8px;
+}
+
+.stat-value {
+ font-size: 1.5em;
+ font-weight: 600;
+ color: #fff;
+ margin-bottom: 4px;
+}
+
+.stat-change {
+ font-size: 0.85em;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.stat-change.positive {
+ color: #3ba55d;
+}
+
+.stat-change.negative {
+ color: #ed4245;
+}
+
+.insights-chart {
+ background: #0f0f0f;
+ border-radius: 8px;
+ padding: 20px;
+ margin-bottom: 20px;
+}
+
+.chart-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 16px;
+}
+
+.chart-header h3 {
+ margin: 0;
+ font-size: 1em;
+}
+
+.chart-legend {
+ display: flex;
+ gap: 16px;
+ font-size: 0.85em;
+}
+
+.legend-item {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.legend-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+}
+
+.chart-placeholder {
+ height: 200px;
+ background: linear-gradient(to right, #1f2023 0%, #2f3136 50%, #1f2023 100%);
+ border-radius: 4px;
+ display: flex;
+ align-items: flex-end;
+ justify-content: space-around;
+ padding: 20px;
+}
+
+.chart-bar {
+ width: 30px;
+ background: #5865f2;
+ border-radius: 4px 4px 0 0;
+ opacity: 0.7;
+}
+
+.top-channels,
+.top-members {
+ margin-top: 20px;
+}
+
+.top-channels h3,
+.top-members h3 {
+ margin: 0 0 12px 0;
+ font-size: 1em;
+}
+
+.top-item {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 10px;
+ background: #0f0f0f;
+ border-radius: 4px;
+ margin-bottom: 8px;
+}
+
+.top-item-rank {
+ width: 24px;
+ height: 24px;
+ background: #2f3136;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.8em;
+ font-weight: 600;
+}
+
+.top-item-name {
+ flex: 1;
+}
+
+.top-item-value {
+ color: #b9bbbe;
+ font-size: 0.9em;
+}
+
+/* ==================== APP DIRECTORY ==================== */
+.app-directory {
+ width: 900px;
+ max-height: 700px;
+ background: #18191c;
+ border-radius: 8px;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+}
+
+.app-directory-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px 20px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.app-directory-header h2 {
+ margin: 0;
+ font-size: 1.25em;
+}
+
+.app-directory-close {
+ width: 32px;
+ height: 32px;
+ background: transparent;
+ border: none;
+ color: #b9bbbe;
+ cursor: pointer;
+ border-radius: 4px;
+ font-size: 1.2em;
+}
+
+.app-directory-close:hover {
+ background: #2f3136;
+ color: #fff;
+}
+
+.app-directory-search {
+ padding: 12px 20px;
+ background: #0f0f0f;
+ border-bottom: 1px solid #2f3136;
+}
+
+.app-search-input {
+ width: 100%;
+ padding: 10px 16px;
+ background: #2f3136;
+ border: none;
+ border-radius: 4px;
+ color: #e0e0e0;
+}
+
+.app-search-input:focus {
+ outline: none;
+ box-shadow: 0 0 0 2px #5865f2;
+}
+
+.app-directory-body {
+ display: flex;
+ flex: 1;
+ overflow: hidden;
+}
+
+.app-categories {
+ width: 180px;
+ background: #0f0f0f;
+ border-right: 1px solid #2f3136;
+ overflow-y: auto;
+}
+
+.app-category {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 12px 16px;
+ cursor: pointer;
+ color: #b9bbbe;
+ transition: all 0.2s;
+}
+
+.app-category:hover {
+ background: #1f2023;
+ color: #fff;
+}
+
+.app-category.active {
+ background: #2f3136;
+ color: #fff;
+}
+
+.category-icon {
+ font-size: 1.2em;
+}
+
+.app-list {
+ flex: 1;
+ overflow-y: auto;
+ padding: 16px;
+}
+
+.app-list-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 12px;
+}
+
+.app-card {
+ background: #0f0f0f;
+ border-radius: 8px;
+ padding: 16px;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.app-card:hover {
+ background: #1f2023;
+ transform: translateY(-2px);
+}
+
+.app-card-header {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 12px;
+}
+
+.app-icon {
+ width: 48px;
+ height: 48px;
+ background: linear-gradient(135deg, #5865f2, #eb459e);
+ border-radius: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.5em;
+}
+
+.app-name {
+ font-weight: 600;
+ margin-bottom: 2px;
+}
+
+.app-developer {
+ font-size: 0.8em;
+ color: #666;
+}
+
+.app-description {
+ font-size: 0.9em;
+ color: #b9bbbe;
+ margin-bottom: 12px;
+ line-height: 1.4;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
+
+.app-tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+}
+
+.app-tag {
+ padding: 4px 8px;
+ background: #2f3136;
+ border-radius: 4px;
+ font-size: 0.75em;
+ color: #b9bbbe;
+}
+
+.app-detail-view {
+ flex: 1;
+ overflow-y: auto;
+ padding: 20px;
+}
+
+.app-detail-header {
+ display: flex;
+ gap: 20px;
+ margin-bottom: 24px;
+}
+
+.app-detail-icon {
+ width: 80px;
+ height: 80px;
+ background: linear-gradient(135deg, #5865f2, #eb459e);
+ border-radius: 16px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 2.5em;
+}
+
+.app-detail-info {
+ flex: 1;
+}
+
+.app-detail-info h3 {
+ margin: 0 0 4px 0;
+ font-size: 1.5em;
+}
+
+.app-detail-developer {
+ color: #666;
+ margin-bottom: 12px;
+}
+
+.app-detail-actions {
+ display: flex;
+ gap: 8px;
+}
+
+.add-app-btn {
+ padding: 10px 20px;
+ background: #5865f2;
+ border: none;
+ border-radius: 4px;
+ color: #fff;
+ cursor: pointer;
+ font-weight: 500;
+}
+
+.add-app-btn:hover {
+ background: #4752c4;
+}
+
+.app-back-btn {
+ padding: 10px 20px;
+ background: #2f3136;
+ border: none;
+ border-radius: 4px;
+ color: #fff;
+ cursor: pointer;
+}
+
+.app-back-btn:hover {
+ background: #3f4248;
+}
+
+.app-detail-description {
+ margin-bottom: 24px;
+ line-height: 1.6;
+ color: #b9bbbe;
+}
+
+.app-detail-features {
+ background: #0f0f0f;
+ border-radius: 8px;
+ padding: 16px;
+}
+
+.app-detail-features h4 {
+ margin: 0 0 12px 0;
+ font-size: 1em;
+}
+
+.feature-list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.feature-list li {
+ padding: 8px 0;
+ color: #b9bbbe;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.feature-list li::before {
+ content: "โ";
+ color: #3ba55d;
+}
+
+/* ==================== SLOW MODE SETTINGS ==================== */
+.slowmode-modal {
+ width: 480px;
+ background: #18191c;
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.slowmode-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px 20px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.slowmode-header h2 {
+ margin: 0;
+ font-size: 1.25em;
+}
+
+.slowmode-close {
+ width: 32px;
+ height: 32px;
+ background: transparent;
+ border: none;
+ color: #b9bbbe;
+ cursor: pointer;
+ border-radius: 4px;
+ font-size: 1.2em;
+}
+
+.slowmode-close:hover {
+ background: #2f3136;
+ color: #fff;
+}
+
+.slowmode-content {
+ padding: 20px;
+}
+
+.slowmode-channel {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 12px 16px;
+ background: #0f0f0f;
+ border-radius: 4px;
+ margin-bottom: 20px;
+}
+
+.slowmode-channel .channel-icon {
+ color: #666;
+ font-weight: 600;
+}
+
+.slowmode-channel .channel-name {
+ font-weight: 500;
+}
+
+.slowmode-section {
+ margin-bottom: 20px;
+}
+
+.slowmode-section h3 {
+ margin: 0 0 8px 0;
+ font-size: 1em;
+}
+
+.slowmode-description {
+ color: #b9bbbe;
+ font-size: 0.9em;
+ margin-bottom: 16px;
+}
+
+.slowmode-presets {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-bottom: 16px;
+}
+
+.preset-btn {
+ padding: 8px 14px;
+ background: #2f3136;
+ border: none;
+ border-radius: 4px;
+ color: #b9bbbe;
+ cursor: pointer;
+ font-size: 0.9em;
+ transition: all 0.2s;
+}
+
+.preset-btn:hover {
+ background: #3f4248;
+ color: #fff;
+}
+
+.preset-btn.active {
+ background: #5865f2;
+ color: #fff;
+}
+
+.custom-input {
+ display: flex;
+ gap: 8px;
+ margin-bottom: 16px;
+}
+
+.custom-input input {
+ flex: 1;
+ padding: 10px 14px;
+ background: #0a0a0a;
+ border: 1px solid #2f3136;
+ border-radius: 4px;
+ color: #e0e0e0;
+}
+
+.custom-input input:focus {
+ outline: none;
+ border-color: #5865f2;
+}
+
+.apply-custom {
+ padding: 10px 16px;
+ background: #5865f2;
+ border: none;
+ border-radius: 4px;
+ color: #fff;
+ cursor: pointer;
+}
+
+.apply-custom:hover {
+ background: #4752c4;
+}
+
+.slowmode-slider {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+}
+
+.slowmode-slider input[type="range"] {
+ flex: 1;
+ height: 6px;
+ background: #2f3136;
+ border-radius: 3px;
+ appearance: none;
+ cursor: pointer;
+}
+
+.slowmode-slider input[type="range"]::-webkit-slider-thumb {
+ appearance: none;
+ width: 16px;
+ height: 16px;
+ background: #5865f2;
+ border-radius: 50%;
+ cursor: pointer;
+}
+
+.slowmode-slider input[type="range"]::-moz-range-thumb {
+ width: 16px;
+ height: 16px;
+ background: #5865f2;
+ border: none;
+ border-radius: 50%;
+ cursor: pointer;
+}
+
+.slider-value {
+ min-width: 80px;
+ color: #b9bbbe;
+ font-size: 0.9em;
+ text-align: right;
+}
+
+.section-header-toggle {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ cursor: pointer;
+ padding: 8px 0;
+}
+
+.section-header-toggle:hover h3 {
+ color: #fff;
+}
+
+.toggle-icon {
+ color: #666;
+ font-size: 0.8em;
+}
+
+.exempt-roles {
+ padding: 12px 0;
+}
+
+.exempt-description {
+ color: #666;
+ font-size: 0.85em;
+ margin-bottom: 12px;
+}
+
+.exempt-role {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 8px 0;
+ cursor: pointer;
+}
+
+.exempt-role input[type="checkbox"] {
+ width: 18px;
+ height: 18px;
+ accent-color: #5865f2;
+}
+
+.exempt-role .role-name {
+ font-weight: 500;
+}
+
+.slowmode-info {
+ display: flex;
+ gap: 10px;
+ padding: 12px;
+ background: #0f0f0f;
+ border-radius: 4px;
+ font-size: 0.85em;
+ color: #b9bbbe;
+ line-height: 1.4;
+}
+
+.info-icon {
+ flex-shrink: 0;
+}
+
+.slowmode-footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: 8px;
+ padding: 16px 20px;
+ background: #0f0f0f;
+ border-top: 1px solid #2f3136;
+}
+
+.slowmode-footer .cancel-btn,
+.slowmode-footer .save-btn {
+ padding: 10px 20px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-weight: 500;
+}
+
+.slowmode-footer .cancel-btn {
+ background: transparent;
+ color: #b9bbbe;
+}
+
+.slowmode-footer .cancel-btn:hover {
+ color: #fff;
+}
+
+.slowmode-footer .save-btn {
+ background: #5865f2;
+ color: #fff;
+}
+
+.slowmode-footer .save-btn:hover {
+ background: #4752c4;
+}
+
+/* ==================== CHANNEL SETTINGS ==================== */
+.channel-settings-modal {
+ display: flex;
+ width: 900px;
+ height: 600px;
+ background: #18191c;
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.settings-channel-info {
+ padding: 16px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.channel-hash {
+ color: #666;
+ font-size: 1.2em;
+ font-weight: 600;
+}
+
+.settings-channel-name {
+ font-weight: 600;
+}
+
+.channel-overview .setting-group {
+ margin-bottom: 24px;
+}
+
+.channel-name-input {
+ display: flex;
+ align-items: center;
+ background: #0a0a0a;
+ border: 1px solid #2f3136;
+ border-radius: 4px;
+ overflow: hidden;
+}
+
+.channel-name-input .input-prefix {
+ padding: 10px 12px;
+ background: #2f3136;
+ color: #666;
+ font-weight: 600;
+}
+
+.channel-name-input input {
+ flex: 1;
+ padding: 10px 12px;
+ background: transparent;
+ border: none;
+ color: #e0e0e0;
+ outline: none;
+}
+
+.setting-textarea {
+ width: 100%;
+ padding: 12px;
+ background: #0a0a0a;
+ border: 1px solid #2f3136;
+ border-radius: 4px;
+ color: #e0e0e0;
+ resize: vertical;
+ font-family: inherit;
+}
+
+.setting-textarea:focus {
+ outline: none;
+ border-color: #5865f2;
+}
+
+.char-count {
+ display: block;
+ text-align: right;
+ font-size: 0.8em;
+ color: #666;
+ margin-top: 4px;
+}
+
+.slowmode-select select {
+ padding: 10px 14px;
+ background: #2f3136;
+ border: none;
+ border-radius: 4px;
+ color: #e0e0e0;
+}
+
+.setting-hint {
+ font-size: 0.85em;
+ color: #666;
+ margin-top: 4px;
+}
+
+.toggle-group {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.toggle-info label {
+ display: block;
+ margin-bottom: 4px;
+}
+
+.toggle-switch {
+ position: relative;
+ display: inline-block;
+ width: 44px;
+ height: 24px;
+}
+
+.toggle-switch input {
+ opacity: 0;
+ width: 0;
+ height: 0;
+}
+
+.toggle-slider {
+ position: absolute;
+ cursor: pointer;
+ inset: 0;
+ background: #72767d;
+ border-radius: 24px;
+ transition: 0.3s;
+}
+
+.toggle-slider::before {
+ position: absolute;
+ content: "";
+ height: 18px;
+ width: 18px;
+ left: 3px;
+ bottom: 3px;
+ background: #fff;
+ border-radius: 50%;
+ transition: 0.3s;
+}
+
+.toggle-switch input:checked + .toggle-slider {
+ background: #3ba55d;
+}
+
+.toggle-switch input:checked + .toggle-slider::before {
+ transform: translateX(20px);
+}
+
+.setting-select {
+ padding: 10px 14px;
+ background: #2f3136;
+ border: none;
+ border-radius: 4px;
+ color: #e0e0e0;
+ width: 100%;
+}
+
+.channel-permissions .permissions-info {
+ padding: 12px;
+ background: #0f0f0f;
+ border-radius: 4px;
+ margin-bottom: 20px;
+ color: #b9bbbe;
+}
+
+.permissions-section {
+ margin-bottom: 24px;
+}
+
+.permissions-section .section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 12px;
+}
+
+.permissions-section h3 {
+ margin: 0;
+ font-size: 1em;
+}
+
+.add-override-btn {
+ padding: 8px 14px;
+ background: #5865f2;
+ border: none;
+ border-radius: 4px;
+ color: #fff;
+ cursor: pointer;
+ font-size: 0.9em;
+}
+
+.add-override-btn:hover {
+ background: #4752c4;
+}
+
+.overrides-list {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.override-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 10px 12px;
+ background: #0f0f0f;
+ border-radius: 4px;
+}
+
+.override-info {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.role-indicator,
+.member-indicator {
+ width: 24px;
+ height: 24px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.8em;
+}
+
+.role-indicator {
+ color: #fff;
+}
+
+.member-indicator {
+ background: #2f3136;
+}
+
+.override-name {
+ font-weight: 500;
+}
+
+.override-type {
+ font-size: 0.75em;
+ color: #666;
+ text-transform: uppercase;
+}
+
+.override-actions {
+ display: flex;
+ gap: 8px;
+}
+
+.edit-override,
+.remove-override {
+ padding: 6px 12px;
+ background: #2f3136;
+ border: none;
+ border-radius: 4px;
+ color: #b9bbbe;
+ cursor: pointer;
+}
+
+.edit-override:hover {
+ background: #3f4248;
+ color: #fff;
+}
+
+.remove-override:hover {
+ background: #ed4245;
+ color: #fff;
+}
+
+.advanced-perms-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 20px;
+}
+
+.perm-category h4 {
+ margin: 0 0 12px 0;
+ font-size: 0.85em;
+ color: #b9bbbe;
+}
+
+.perm-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 8px 12px;
+ background: #0f0f0f;
+ border-radius: 4px;
+ margin-bottom: 6px;
+}
+
+.perm-toggles {
+ display: flex;
+ gap: 4px;
+}
+
+.perm-btn {
+ width: 28px;
+ height: 28px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 0.9em;
+ background: #2f3136;
+ color: #666;
+}
+
+.perm-btn.allow:hover,
+.perm-btn.allow.active {
+ background: #3ba55d;
+ color: #fff;
+}
+
+.perm-btn.neutral:hover,
+.perm-btn.neutral.active {
+ background: #72767d;
+ color: #fff;
+}
+
+.perm-btn.deny:hover,
+.perm-btn.deny.active {
+ background: #ed4245;
+ color: #fff;
+}
+
+.channel-invites,
+.channel-integrations {
+ padding: 20px 0;
+}
+
+.no-invites {
+ text-align: center;
+ color: #666;
+ padding: 40px;
+}
+
+.create-invite-btn,
+.create-webhook-btn {
+ margin-top: 16px;
+ padding: 10px 20px;
+ background: #5865f2;
+ border: none;
+ border-radius: 4px;
+ color: #fff;
+ cursor: pointer;
+}
+
+.integration-section h3 {
+ margin: 0 0 8px 0;
+}
+
+.integration-desc {
+ color: #b9bbbe;
+ font-size: 0.9em;
+}
+
+.settings-save-bar {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 12px 20px;
+ background: #18191c;
+ border-top: 1px solid #2f3136;
+}
+
+.settings-save-bar span {
+ color: #b9bbbe;
+}
+
+.save-actions {
+ display: flex;
+ gap: 8px;
+}
+
+.reset-btn {
+ padding: 8px 16px;
+ background: transparent;
+ border: none;
+ color: #b9bbbe;
+ cursor: pointer;
+}
+
+.reset-btn:hover {
+ text-decoration: underline;
+}
+
+/* ==================== CATEGORY EDITOR ==================== */
+.category-editor {
+ width: 480px;
+ background: #18191c;
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.category-editor-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px 20px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.category-editor-header h2 {
+ margin: 0;
+ font-size: 1.25em;
+}
+
+.category-close {
+ width: 32px;
+ height: 32px;
+ background: transparent;
+ border: none;
+ color: #b9bbbe;
+ cursor: pointer;
+ border-radius: 4px;
+ font-size: 1.2em;
+}
+
+.category-close:hover {
+ background: #2f3136;
+ color: #fff;
+}
+
+.category-editor-content {
+ padding: 20px;
+}
+
+.category-name-input {
+ width: 100%;
+ padding: 12px 14px;
+ background: #0a0a0a;
+ border: 1px solid #2f3136;
+ border-radius: 4px;
+ color: #e0e0e0;
+ font-size: 1em;
+ text-transform: uppercase;
+}
+
+.category-name-input:focus {
+ outline: none;
+ border-color: #5865f2;
+}
+
+.role-selector {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ margin-top: 8px;
+}
+
+.role-checkbox {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 8px 12px;
+ background: #0f0f0f;
+ border-radius: 4px;
+ cursor: pointer;
+ transition: background 0.2s;
+}
+
+.role-checkbox:hover {
+ background: #1f2023;
+}
+
+.role-checkbox.checked {
+ background: rgba(88, 101, 242, 0.2);
+}
+
+.role-checkbox input {
+ width: 18px;
+ height: 18px;
+ accent-color: #5865f2;
+}
+
+.role-color {
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+}
+
+.category-channels {
+ margin-top: 20px;
+}
+
+.channel-list-preview {
+ margin-top: 8px;
+ background: #0f0f0f;
+ border-radius: 4px;
+ padding: 8px;
+}
+
+.channel-preview-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 6px 8px;
+ color: #b9bbbe;
+ font-size: 0.9em;
+}
+
+.channel-preview-item .channel-icon {
+ color: #666;
+}
+
+.category-editor-footer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px 20px;
+ background: #0f0f0f;
+ border-top: 1px solid #2f3136;
+}
+
+.delete-category-btn {
+ padding: 10px 16px;
+ background: transparent;
+ border: none;
+ color: #ed4245;
+ cursor: pointer;
+}
+
+.delete-category-btn:hover {
+ text-decoration: underline;
+}
+
+.footer-actions {
+ display: flex;
+ gap: 8px;
+}
+
+/* ==================== CHANNEL PERMISSIONS MODAL ==================== */
+.channel-permissions-modal {
+ width: 700px;
+ max-height: 80vh;
+ background: #18191c;
+ border-radius: 8px;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+}
+
+.permissions-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ padding: 16px 20px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.permissions-title h2 {
+ margin: 0 0 4px 0;
+ font-size: 1.25em;
+}
+
+.permissions-context {
+ font-size: 0.9em;
+ color: #b9bbbe;
+}
+
+.channel-ref {
+ color: #666;
+ margin-left: 8px;
+}
+
+.permissions-close {
+ width: 32px;
+ height: 32px;
+ background: transparent;
+ border: none;
+ color: #b9bbbe;
+ cursor: pointer;
+ border-radius: 4px;
+ font-size: 1.2em;
+}
+
+.permissions-close:hover {
+ background: #2f3136;
+ color: #fff;
+}
+
+.permissions-toolbar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 12px 20px;
+ background: #0f0f0f;
+ border-bottom: 1px solid #2f3136;
+}
+
+.permissions-search {
+ flex: 1;
+ max-width: 300px;
+ padding: 8px 12px;
+ background: #2f3136;
+ border: none;
+ border-radius: 4px;
+ color: #e0e0e0;
+}
+
+.permissions-search:focus {
+ outline: none;
+ box-shadow: 0 0 0 2px #5865f2;
+}
+
+.bulk-actions {
+ display: flex;
+ gap: 8px;
+}
+
+.bulk-actions button {
+ padding: 6px 12px;
+ background: #2f3136;
+ border: none;
+ border-radius: 4px;
+ color: #b9bbbe;
+ cursor: pointer;
+ font-size: 0.85em;
+}
+
+.bulk-actions button:hover {
+ background: #3f4248;
+ color: #fff;
+}
+
+.permissions-legend {
+ display: flex;
+ gap: 20px;
+ padding: 12px 20px;
+ background: #0f0f0f;
+ border-bottom: 1px solid #2f3136;
+}
+
+.legend-item {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 0.85em;
+ color: #b9bbbe;
+}
+
+.legend-icon {
+ width: 20px;
+ height: 20px;
+ border-radius: 4px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.8em;
+}
+
+.legend-icon.allow {
+ background: #3ba55d;
+ color: #fff;
+}
+
+.legend-icon.neutral {
+ background: #72767d;
+ color: #fff;
+}
+
+.legend-icon.deny {
+ background: #ed4245;
+ color: #fff;
+}
+
+.permissions-list {
+ flex: 1;
+ overflow-y: auto;
+ padding: 16px 20px;
+}
+
+.permission-category {
+ margin-bottom: 24px;
+}
+
+.permission-category h3 {
+ margin: 0 0 12px 0;
+ font-size: 0.9em;
+ color: #b9bbbe;
+ text-transform: uppercase;
+}
+
+.permission-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 12px;
+ background: #0f0f0f;
+ border-radius: 4px;
+ margin-bottom: 6px;
+}
+
+.permission-info {
+ flex: 1;
+}
+
+.permission-label {
+ display: block;
+ font-weight: 500;
+ margin-bottom: 2px;
+}
+
+.permission-desc {
+ font-size: 0.8em;
+ color: #666;
+}
+
+.permission-toggles {
+ display: flex;
+ gap: 4px;
+}
+
+.permissions-footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: 8px;
+ padding: 16px 20px;
+ background: #0f0f0f;
+ border-top: 1px solid #2f3136;
+}
+
+/* ==================== MEMBER LIST PANEL ==================== */
+.member-list-panel {
+ width: 400px;
+ height: 100%;
+ background: #18191c;
+ border-left: 1px solid #2f3136;
+ display: flex;
+ flex-direction: column;
+}
+
+.member-list-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.member-list-header h2 {
+ margin: 0;
+ font-size: 1.1em;
+}
+
+.member-list-close {
+ width: 28px;
+ height: 28px;
+ background: transparent;
+ border: none;
+ color: #b9bbbe;
+ cursor: pointer;
+ border-radius: 4px;
+}
+
+.member-list-close:hover {
+ background: #2f3136;
+ color: #fff;
+}
+
+.member-list-filters {
+ display: flex;
+ gap: 8px;
+ padding: 12px 16px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.member-search {
+ flex: 1;
+ padding: 8px 12px;
+ background: #0a0a0a;
+ border: 1px solid #2f3136;
+ border-radius: 4px;
+ color: #e0e0e0;
+}
+
+.member-search:focus {
+ outline: none;
+ border-color: #5865f2;
+}
+
+.role-filter {
+ padding: 8px 12px;
+ background: #2f3136;
+ border: none;
+ border-radius: 4px;
+ color: #e0e0e0;
+}
+
+.member-list-content {
+ flex: 1;
+ overflow-y: auto;
+}
+
+.no-members {
+ padding: 40px;
+ text-align: center;
+ color: #666;
+}
+
+.member-list-item {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 10px 16px;
+ cursor: pointer;
+ transition: background 0.2s;
+ position: relative;
+}
+
+.member-list-item:hover {
+ background: #1f2023;
+}
+
+.member-list-item.selected {
+ background: #2f3136;
+}
+
+.member-avatar-wrapper {
+ position: relative;
+}
+
+.member-avatar {
+ width: 40px;
+ height: 40px;
+ background: linear-gradient(135deg, #5865f2, #eb459e);
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: 600;
+}
+
+.member-status-dot {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ width: 14px;
+ height: 14px;
+ border-radius: 50%;
+ border: 3px solid #18191c;
+}
+
+.member-info {
+ flex: 1;
+ min-width: 0;
+}
+
+.member-name-row {
+ display: flex;
+ align-items: baseline;
+ gap: 4px;
+}
+
+.member-name {
+ font-weight: 500;
+}
+
+.member-tag {
+ font-size: 0.8em;
+ color: #666;
+}
+
+.member-roles {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px;
+ margin-top: 4px;
+}
+
+.member-role-badge {
+ padding: 2px 6px;
+ border: 1px solid;
+ border-radius: 4px;
+ font-size: 0.7em;
+}
+
+.member-actions {
+ display: flex;
+ gap: 4px;
+ opacity: 0;
+ transition: opacity 0.2s;
+}
+
+.member-list-item:hover .member-actions {
+ opacity: 1;
+}
+
+.member-action-btn {
+ width: 28px;
+ height: 28px;
+ background: transparent;
+ border: none;
+ cursor: pointer;
+ border-radius: 4px;
+}
+
+.member-action-btn:hover {
+ background: #3f4248;
+}
+
+.role-assignment-menu {
+ position: absolute;
+ top: 100%;
+ right: 16px;
+ background: #18191c;
+ border: 1px solid #2f3136;
+ border-radius: 4px;
+ padding: 8px 0;
+ z-index: 10;
+ min-width: 180px;
+}
+
+.role-menu-header {
+ padding: 8px 12px;
+ font-size: 0.8em;
+ font-weight: 600;
+ color: #666;
+ text-transform: uppercase;
+}
+
+.role-menu-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 12px;
+ cursor: pointer;
+}
+
+.role-menu-item:hover {
+ background: #2f3136;
+}
+
+.role-menu-item input {
+ width: 16px;
+ height: 16px;
+ accent-color: #5865f2;
+}
+
+.role-dot {
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+}
+
+.member-list-footer {
+ display: flex;
+ gap: 8px;
+ padding: 12px 16px;
+ border-top: 1px solid #2f3136;
+}
+
+.prune-members-btn {
+ flex: 1;
+ padding: 8px;
+ background: transparent;
+ border: 1px solid #2f3136;
+ border-radius: 4px;
+ color: #b9bbbe;
+ cursor: pointer;
+}
+
+.prune-members-btn:hover {
+ background: #2f3136;
+ color: #fff;
+}
+
+.invite-members-btn {
+ flex: 1;
+ padding: 8px;
+ background: #5865f2;
+ border: none;
+ border-radius: 4px;
+ color: #fff;
+ cursor: pointer;
+}
+
+.invite-members-btn:hover {
+ background: #4752c4;
+}
+
+/* ==================== BAN LIST ==================== */
+.ban-list-modal {
+ width: 600px;
+ max-height: 600px;
+ background: #18191c;
+ border-radius: 8px;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+}
+
+.ban-list-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px 20px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.ban-list-header h2 {
+ margin: 0;
+ font-size: 1.25em;
+}
+
+.ban-list-close {
+ width: 32px;
+ height: 32px;
+ background: transparent;
+ border: none;
+ color: #b9bbbe;
+ cursor: pointer;
+ border-radius: 4px;
+ font-size: 1.2em;
+}
+
+.ban-list-close:hover {
+ background: #2f3136;
+ color: #fff;
+}
+
+.ban-list-search {
+ padding: 12px 20px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.ban-list-search input {
+ width: 100%;
+ padding: 10px 14px;
+ background: #0a0a0a;
+ border: 1px solid #2f3136;
+ border-radius: 4px;
+ color: #e0e0e0;
+}
+
+.ban-list-search input:focus {
+ outline: none;
+ border-color: #5865f2;
+}
+
+.ban-list-content {
+ flex: 1;
+ overflow-y: auto;
+ padding: 12px 20px;
+}
+
+.ban-count {
+ font-size: 0.85em;
+ color: #666;
+ margin-bottom: 12px;
+}
+
+.no-bans {
+ text-align: center;
+ padding: 40px;
+}
+
+.no-bans-icon {
+ font-size: 3em;
+ display: block;
+ margin-bottom: 12px;
+}
+
+.no-bans p {
+ margin: 0 0 4px 0;
+ color: #b9bbbe;
+}
+
+.no-bans-hint {
+ font-size: 0.85em;
+ color: #666;
+}
+
+.ban-item {
+ display: flex;
+ flex-direction: column;
+ padding: 12px;
+ background: #0f0f0f;
+ border-radius: 4px;
+ margin-bottom: 8px;
+ cursor: pointer;
+ transition: background 0.2s;
+}
+
+.ban-item:hover {
+ background: #1f2023;
+}
+
+.ban-item.selected {
+ background: #2f3136;
+}
+
+.ban-user-info {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 8px;
+}
+
+.ban-avatar {
+ width: 36px;
+ height: 36px;
+ background: #ed4245;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: 600;
+}
+
+.ban-user-details {
+ flex: 1;
+}
+
+.ban-username {
+ font-weight: 500;
+}
+
+.ban-tag {
+ color: #666;
+ font-size: 0.9em;
+}
+
+.ban-reason {
+ font-size: 0.85em;
+ color: #b9bbbe;
+ margin-top: 2px;
+}
+
+.ban-meta {
+ display: flex;
+ justify-content: space-between;
+ font-size: 0.8em;
+ color: #666;
+}
+
+.ban-actions {
+ margin-top: 12px;
+ padding-top: 12px;
+ border-top: 1px solid #2f3136;
+}
+
+.revoke-ban-btn {
+ width: 100%;
+ padding: 10px;
+ background: #ed4245;
+ border: none;
+ border-radius: 4px;
+ color: #fff;
+ cursor: pointer;
+}
+
+.revoke-ban-btn:hover {
+ background: #c73e40;
+}
+
+.revoke-confirm-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(0,0,0,0.7);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1001;
+}
+
+.revoke-confirm-modal {
+ background: #18191c;
+ border-radius: 8px;
+ padding: 20px;
+ width: 400px;
+}
+
+.revoke-confirm-modal h3 {
+ margin: 0 0 12px 0;
+}
+
+.revoke-warning {
+ color: #faa61a;
+ font-size: 0.9em;
+ margin-top: 12px;
+}
+
+.revoke-actions {
+ display: flex;
+ justify-content: flex-end;
+ gap: 8px;
+ margin-top: 20px;
+}
+
+.confirm-revoke-btn {
+ padding: 10px 20px;
+ background: #ed4245;
+ border: none;
+ border-radius: 4px;
+ color: #fff;
+ cursor: pointer;
+}
+
+.confirm-revoke-btn:hover {
+ background: #c73e40;
+}
+
+/* ==================== INVITE MANAGER ==================== */
+.invite-manager-modal {
+ width: 800px;
+ max-height: 600px;
+ background: #18191c;
+ border-radius: 8px;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+}
+
+.invite-manager-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px 20px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.invite-manager-header h2 {
+ margin: 0;
+ font-size: 1.25em;
+}
+
+.invite-manager-close {
+ width: 32px;
+ height: 32px;
+ background: transparent;
+ border: none;
+ color: #b9bbbe;
+ cursor: pointer;
+ border-radius: 4px;
+ font-size: 1.2em;
+}
+
+.invite-manager-close:hover {
+ background: #2f3136;
+ color: #fff;
+}
+
+.invite-manager-actions {
+ padding: 12px 20px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.invite-list {
+ flex: 1;
+ overflow-y: auto;
+ padding: 0 20px;
+}
+
+.no-invites {
+ text-align: center;
+ padding: 40px;
+ color: #666;
+}
+
+.invite-list-header {
+ display: grid;
+ grid-template-columns: 150px 120px 80px 80px 100px 50px;
+ gap: 12px;
+ padding: 12px 0;
+ font-size: 0.75em;
+ text-transform: uppercase;
+ color: #666;
+ font-weight: 600;
+ border-bottom: 1px solid #2f3136;
+}
+
+.invite-item {
+ display: grid;
+ grid-template-columns: 150px 120px 80px 80px 100px 50px;
+ gap: 12px;
+ align-items: center;
+ padding: 12px 0;
+ border-bottom: 1px solid #0f0f0f;
+}
+
+.invite-code {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.invite-code code {
+ background: #0f0f0f;
+ padding: 4px 8px;
+ border-radius: 4px;
+ font-family: 'Roboto Mono', monospace;
+}
+
+.copy-btn {
+ width: 24px;
+ height: 24px;
+ background: transparent;
+ border: none;
+ cursor: pointer;
+ border-radius: 4px;
+}
+
+.copy-btn:hover {
+ background: #2f3136;
+}
+
+.copy-btn.copied {
+ color: #3ba55d;
+}
+
+.invite-channel {
+ color: #b9bbbe;
+}
+
+.invite-uses {
+ color: #b9bbbe;
+}
+
+.invite-expires {
+ color: #b9bbbe;
+}
+
+.invite-creator {
+ color: #666;
+}
+
+.delete-invite-btn {
+ width: 28px;
+ height: 28px;
+ background: transparent;
+ border: none;
+ cursor: pointer;
+ border-radius: 4px;
+ color: #666;
+}
+
+.delete-invite-btn:hover {
+ background: #ed4245;
+ color: #fff;
+}
+
+.invite-manager-footer {
+ padding: 12px 20px;
+ border-top: 1px solid #2f3136;
+}
+
+.invite-hint {
+ font-size: 0.85em;
+ color: #666;
+}
+
+.create-invite-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(0,0,0,0.7);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1001;
+}
+
+.create-invite-modal {
+ background: #18191c;
+ border-radius: 8px;
+ padding: 20px;
+ width: 400px;
+}
+
+.create-invite-modal h3 {
+ margin: 0 0 20px 0;
+}
+
+.create-field {
+ margin-bottom: 16px;
+}
+
+.create-field label {
+ display: block;
+ margin-bottom: 6px;
+ font-size: 0.85em;
+ color: #b9bbbe;
+}
+
+.create-field select {
+ width: 100%;
+ padding: 10px 12px;
+ background: #2f3136;
+ border: none;
+ border-radius: 4px;
+ color: #e0e0e0;
+}
+
+.create-actions {
+ display: flex;
+ justify-content: flex-end;
+ gap: 8px;
+ margin-top: 20px;
+}
+
+.generate-btn {
+ padding: 10px 20px;
+ background: #5865f2;
+ border: none;
+ border-radius: 4px;
+ color: #fff;
+ cursor: pointer;
+}
+
+.generate-btn:hover {
+ background: #4752c4;
+}
+
+/* ==================== NOTIFICATION SETTINGS ==================== */
+.notification-settings-modal {
+ width: 480px;
+ background: #18191c;
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.notif-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px 20px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.notif-header h2 {
+ margin: 0;
+ font-size: 1.25em;
+}
+
+.notif-close {
+ width: 32px;
+ height: 32px;
+ background: transparent;
+ border: none;
+ color: #b9bbbe;
+ cursor: pointer;
+ border-radius: 4px;
+ font-size: 1.2em;
+}
+
+.notif-close:hover {
+ background: #2f3136;
+ color: #fff;
+}
+
+.notif-target {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 16px 20px;
+ background: #0f0f0f;
+ border-bottom: 1px solid #2f3136;
+}
+
+.target-icon {
+ font-size: 1.2em;
+ color: #666;
+}
+
+.target-name {
+ font-weight: 600;
+}
+
+.notif-content {
+ padding: 20px;
+ max-height: 400px;
+ overflow-y: auto;
+}
+
+.notif-section {
+ margin-bottom: 24px;
+}
+
+.notif-section h3 {
+ margin: 0 0 12px 0;
+ font-size: 1em;
+}
+
+.notif-options {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.notif-option {
+ display: flex;
+ align-items: flex-start;
+ gap: 12px;
+ padding: 12px;
+ background: #0f0f0f;
+ border-radius: 4px;
+ cursor: pointer;
+ transition: background 0.2s;
+}
+
+.notif-option:hover {
+ background: #1f2023;
+}
+
+.notif-option.selected {
+ background: rgba(88, 101, 242, 0.2);
+ border: 1px solid #5865f2;
+}
+
+.notif-option input {
+ margin-top: 2px;
+ accent-color: #5865f2;
+}
+
+.option-content {
+ flex: 1;
+}
+
+.option-label {
+ display: block;
+ font-weight: 500;
+ margin-bottom: 2px;
+}
+
+.option-desc {
+ font-size: 0.85em;
+ color: #666;
+}
+
+.mute-select {
+ width: 100%;
+ padding: 10px 14px;
+ background: #2f3136;
+ border: none;
+ border-radius: 4px;
+ color: #e0e0e0;
+}
+
+.suppress-options {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.suppress-option {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ cursor: pointer;
+}
+
+.suppress-option input {
+ width: 18px;
+ height: 18px;
+ accent-color: #5865f2;
+}
+
+.toggle-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.toggle-row h3 {
+ margin: 0;
+}
+
+.toggle-row p {
+ margin: 4px 0 0 0;
+ font-size: 0.85em;
+ color: #666;
+}
+
+.channel-overrides {
+ padding-top: 20px;
+ border-top: 1px solid #2f3136;
+}
+
+.override-hint {
+ font-size: 0.85em;
+ color: #666;
+ margin-bottom: 12px;
+}
+
+.override-list {
+ background: #0f0f0f;
+ border-radius: 4px;
+ margin-bottom: 12px;
+}
+
+.override-item {
+ display: flex;
+ justify-content: space-between;
+ padding: 10px 12px;
+ border-bottom: 1px solid #18191c;
+}
+
+.override-item:last-child {
+ border-bottom: none;
+}
+
+.override-value {
+ color: #666;
+}
+
+.notif-footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: 8px;
+ padding: 16px 20px;
+ background: #0f0f0f;
+ border-top: 1px solid #2f3136;
+}
+
+/* ==================== SERVER TEMPLATE ==================== */
+.server-template-modal {
+ width: 700px;
+ max-height: 80vh;
+ background: #18191c;
+ border-radius: 8px;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+}
+
+.template-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px 20px;
+ border-bottom: 1px solid #2f3136;
+}
+
+.template-header h2 {
+ margin: 0;
+ font-size: 1.25em;
+}
+
+.template-close {
+ width: 32px;
+ height: 32px;
+ background: transparent;
+ border: none;
+ color: #b9bbbe;
+ cursor: pointer;
+ border-radius: 4px;
+ font-size: 1.2em;
+}
+
+.template-close:hover {
+ background: #2f3136;
+ color: #fff;
+}
+
+.template-tabs {
+ display: flex;
+ border-bottom: 1px solid #2f3136;
+}
+
+.template-tab {
+ flex: 1;
+ padding: 12px;
+ background: transparent;
+ border: none;
+ color: #b9bbbe;
+ cursor: pointer;
+ border-bottom: 2px solid transparent;
+}
+
+.template-tab:hover {
+ color: #fff;
+}
+
+.template-tab.active {
+ color: #fff;
+ border-bottom-color: #5865f2;
+}
+
+.template-content {
+ flex: 1;
+ overflow-y: auto;
+ padding: 20px;
+}
+
+.template-intro {
+ color: #b9bbbe;
+ margin-bottom: 20px;
+}
+
+.template-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 12px;
+}
+
+.template-card {
+ background: #0f0f0f;
+ border-radius: 8px;
+ padding: 16px;
+ cursor: pointer;
+ transition: all 0.2s;
+ border: 2px solid transparent;
+}
+
+.template-card:hover {
+ background: #1f2023;
+}
+
+.template-card.selected {
+ border-color: #5865f2;
+ background: rgba(88, 101, 242, 0.1);
+}
+
+.template-card .template-icon {
+ font-size: 2em;
+ margin-bottom: 12px;
+}
+
+.template-card .template-info h3 {
+ margin: 0 0 4px 0;
+ font-size: 1.1em;
+}
+
+.template-card .template-info p {
+ margin: 0;
+ font-size: 0.85em;
+ color: #666;
+}
+
+.template-preview {
+ display: flex;
+ gap: 16px;
+ margin-top: 12px;
+ padding-top: 12px;
+ border-top: 1px solid #2f3136;
+}
+
+.preview-section {
+ display: flex;
+ gap: 6px;
+ font-size: 0.85em;
+}
+
+.preview-label {
+ color: #666;
+}
+
+.preview-count {
+ font-weight: 600;
+}
+
+.template-detail {
+ margin-top: 20px;
+ padding: 16px;
+ background: #0f0f0f;
+ border-radius: 8px;
+}
+
+.template-detail h3 {
+ margin: 0 0 16px 0;
+}
+
+.detail-section {
+ margin-bottom: 16px;
+}
+
+.detail-section:last-child {
+ margin-bottom: 0;
+}
+
+.detail-section h4 {
+ margin: 0 0 8px 0;
+ font-size: 0.85em;
+ color: #b9bbbe;
+}
+
+.detail-items {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+}
+
+.detail-item {
+ padding: 4px 10px;
+ background: #2f3136;
+ border-radius: 4px;
+ font-size: 0.85em;
+}
+
+.detail-item.role {
+ color: #5865f2;
+}
+
+.my-templates .no-templates {
+ text-align: center;
+ padding: 40px;
+}
+
+.no-templates-icon {
+ font-size: 3em;
+ display: block;
+ margin-bottom: 12px;
+}
+
+.no-templates p {
+ margin: 0 0 4px 0;
+ color: #b9bbbe;
+}
+
+.no-templates span {
+ font-size: 0.85em;
+ color: #666;
+}
+
+.template-list-item {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ padding: 16px;
+ background: #0f0f0f;
+ border-radius: 8px;
+ margin-bottom: 12px;
+ cursor: pointer;
+ border: 2px solid transparent;
+}
+
+.template-list-item:hover {
+ background: #1f2023;
+}
+
+.template-list-item.selected {
+ border-color: #5865f2;
+}
+
+.template-list-item .template-icon {
+ font-size: 2em;
+}
+
+.template-list-item .template-info {
+ flex: 1;
+}
+
+.template-list-item .template-info h3 {
+ margin: 0 0 4px 0;
+}
+
+.template-list-item .template-info p {
+ margin: 0 0 4px 0;
+ font-size: 0.85em;
+ color: #666;
+}
+
+.template-date {
+ font-size: 0.8em;
+ color: #666;
+}
+
+.template-action-btn {
+ width: 32px;
+ height: 32px;
+ background: transparent;
+ border: none;
+ cursor: pointer;
+ border-radius: 4px;
+}
+
+.template-action-btn:hover {
+ background: #2f3136;
+}
+
+.create-template .create-intro {
+ color: #b9bbbe;
+ margin-bottom: 20px;
+}
+
+.create-form .form-group {
+ margin-bottom: 20px;
+}
+
+.create-form .form-group label {
+ display: block;
+ margin-bottom: 8px;
+ font-weight: 500;
+}
+
+.create-form input[type="text"],
+.create-form textarea {
+ width: 100%;
+ padding: 12px;
+ background: #0a0a0a;
+ border: 1px solid #2f3136;
+ border-radius: 4px;
+ color: #e0e0e0;
+ font-family: inherit;
+}
+
+.create-form input:focus,
+.create-form textarea:focus {
+ outline: none;
+ border-color: #5865f2;
+}
+
+.include-options {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.include-option {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ cursor: pointer;
+}
+
+.include-option input {
+ width: 18px;
+ height: 18px;
+ accent-color: #5865f2;
+}
+
+.template-warning {
+ display: flex;
+ gap: 10px;
+ padding: 12px;
+ background: rgba(250, 166, 26, 0.1);
+ border: 1px solid #faa61a;
+ border-radius: 4px;
+ color: #faa61a;
+ font-size: 0.9em;
+}
+
+.template-footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: 8px;
+ padding: 16px 20px;
+ background: #0f0f0f;
+ border-top: 1px solid #2f3136;
+}
+
+.apply-btn {
+ padding: 10px 20px;
+ background: #5865f2;
+ border: none;
+ border-radius: 4px;
+ color: #fff;
+ cursor: pointer;
+ font-weight: 500;
+}
+
+.apply-btn:hover {
+ background: #4752c4;
+}
+
+.apply-btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+/* ==================== LANDING PAGE ==================== */
+.landing-page {
+ min-height: 100vh;
+ background: #0a0a0a;
+ color: #e0e0e0;
+}
+
+.landing-nav {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 20px 60px;
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ background: rgba(10, 10, 10, 0.9);
+ backdrop-filter: blur(10px);
+ z-index: 100;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
+}
+
+.nav-brand {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ font-size: 1.25em;
+ font-weight: 700;
+ color: #fff;
+ text-decoration: none;
+}
+
+.brand-icon {
+ font-size: 1.5em;
+ color: #ff0000;
+}
+
+.nav-links {
+ display: flex;
+ gap: 32px;
+}
+
+.nav-links a {
+ color: #b9bbbe;
+ text-decoration: none;
+ font-weight: 500;
+ transition: color 0.2s;
+}
+
+.nav-links a:hover,
+.nav-links a.active {
+ color: #fff;
+}
+
+.nav-auth {
+ display: flex;
+ gap: 12px;
+}
+
+.nav-login {
+ padding: 10px 20px;
+ color: #b9bbbe;
+ text-decoration: none;
+ font-weight: 500;
+ transition: color 0.2s;
+}
+
+.nav-login:hover {
+ color: #fff;
+}
+
+.nav-register {
+ padding: 10px 24px;
+ background: #5865f2;
+ color: #fff;
+ text-decoration: none;
+ border-radius: 4px;
+ font-weight: 500;
+ transition: background 0.2s;
+}
+
+.nav-register:hover {
+ background: #4752c4;
+}
+
+.hero {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ min-height: 100vh;
+ padding: 120px 60px 60px;
+ gap: 60px;
+}
+
+.hero-content {
+ flex: 1;
+ max-width: 600px;
+}
+
+.hero-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 16px;
+ background: rgba(255, 255, 255, 0.05);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ border-radius: 100px;
+ font-size: 0.85em;
+ color: #b9bbbe;
+ margin-bottom: 24px;
+}
+
+.badge-dot {
+ width: 8px;
+ height: 8px;
+ background: #3ba55d;
+ border-radius: 50%;
+ animation: pulse 2s infinite;
+}
+
+@keyframes pulse {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.5; }
+}
+
+.hero-title {
+ font-size: 4em;
+ font-weight: 800;
+ line-height: 1.1;
+ margin: 0 0 24px;
+ color: #fff;
+}
+
+.gradient-text {
+ background: linear-gradient(135deg, #ff0000, #0066ff, #ffa500);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.hero-subtitle {
+ font-size: 1.25em;
+ color: #b9bbbe;
+ line-height: 1.6;
+ margin-bottom: 32px;
+}
+
+.hero-actions {
+ display: flex;
+ gap: 16px;
+ margin-bottom: 48px;
+}
+
+.cta-primary {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 16px 32px;
+ background: linear-gradient(135deg, #ff0000, #cc0000);
+ color: #fff;
+ text-decoration: none;
+ border-radius: 4px;
+ font-weight: 600;
+ font-size: 1.1em;
+ transition: all 0.2s;
+}
+
+.cta-primary:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 10px 30px rgba(255, 0, 0, 0.3);
+}
+
+.cta-arrow {
+ transition: transform 0.2s;
+}
+
+.cta-primary:hover .cta-arrow {
+ transform: translateX(4px);
+}
+
+.cta-secondary {
+ display: inline-flex;
+ align-items: center;
+ padding: 16px 32px;
+ background: transparent;
+ border: 1px solid #333;
+ color: #b9bbbe;
+ text-decoration: none;
+ border-radius: 4px;
+ font-weight: 500;
+ transition: all 0.2s;
+}
+
+.cta-secondary:hover {
+ background: rgba(255, 255, 255, 0.05);
+ border-color: #555;
+ color: #fff;
+}
+
+.hero-stats {
+ display: flex;
+ align-items: center;
+ gap: 32px;
+}
+
+.stat {
+ text-align: center;
+}
+
+.stat-value {
+ display: block;
+ font-size: 1.5em;
+ font-weight: 700;
+ color: #fff;
+}
+
+.stat-label {
+ font-size: 0.85em;
+ color: #666;
+}
+
+.stat-divider {
+ width: 1px;
+ height: 40px;
+ background: #333;
+}
+
+.hero-visual {
+ flex: 1;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.app-preview {
+ width: 500px;
+ height: 350px;
+ background: #18191c;
+ border-radius: 12px;
+ overflow: hidden;
+ border: 1px solid #2f3136;
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
+}
+
+.preview-header {
+ padding: 12px 16px;
+ background: #0f0f0f;
+ border-bottom: 1px solid #2f3136;
+}
+
+.preview-dots {
+ display: flex;
+ gap: 8px;
+}
+
+.preview-dots span {
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ background: #333;
+}
+
+.preview-dots span:first-child { background: #ff5f56; }
+.preview-dots span:nth-child(2) { background: #ffbd2e; }
+.preview-dots span:last-child { background: #27c93f; }
+
+.preview-content {
+ display: flex;
+ height: calc(100% - 40px);
+}
+
+.preview-sidebar {
+ width: 72px;
+ background: #0f0f0f;
+ padding: 12px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.preview-server {
+ width: 48px;
+ height: 48px;
+ background: #2f3136;
+ border-radius: 50%;
+ transition: border-radius 0.2s;
+}
+
+.preview-server.active {
+ background: linear-gradient(135deg, #ff0000, #cc0000);
+ border-radius: 16px;
+}
+
+.preview-channels {
+ width: 200px;
+ background: #18191c;
+ padding: 16px;
+}
+
+.preview-channel {
+ height: 28px;
+ background: #2f3136;
+ border-radius: 4px;
+ margin-bottom: 8px;
+ opacity: 0.5;
+}
+
+.preview-channel.active {
+ background: rgba(255, 255, 255, 0.1);
+ opacity: 1;
+}
+
+.preview-chat {
+ flex: 1;
+ padding: 16px;
+}
+
+.preview-message {
+ height: 60px;
+ background: #0f0f0f;
+ border-radius: 4px;
+ margin-bottom: 12px;
+}
+
+.preview-message.short {
+ width: 70%;
+}
+
+.features-section {
+ padding: 100px 60px;
+ background: #0f0f0f;
+}
+
+.section-title {
+ font-size: 2.5em;
+ text-align: center;
+ margin-bottom: 16px;
+ color: #fff;
+}
+
+.section-subtitle {
+ text-align: center;
+ color: #666;
+ font-size: 1.1em;
+ margin-bottom: 60px;
+}
+
+.features-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 32px;
+ max-width: 1200px;
+ margin: 0 auto;
+}
+
+.feature-card {
+ padding: 32px;
+ background: #18191c;
+ border-radius: 12px;
+ border: 1px solid #2f3136;
+ transition: all 0.3s;
+}
+
+.feature-card:hover {
+ transform: translateY(-4px);
+ border-color: #5865f2;
+}
+
+.feature-icon {
+ font-size: 2.5em;
+ margin-bottom: 16px;
+ display: block;
+}
+
+.feature-card h3 {
+ margin: 0 0 8px;
+ font-size: 1.25em;
+ color: #fff;
+}
+
+.feature-card p {
+ margin: 0;
+ color: #b9bbbe;
+ line-height: 1.5;
+}
+
+/* ==================== AUTH PAGES ==================== */
+.auth-page {
+ min-height: 100vh;
+ display: flex;
+ background: #0a0a0a;
+}
+
+.auth-container {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 40px;
+}
+
+.auth-header {
+ margin-bottom: 40px;
+}
+
+.auth-brand {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ color: #fff;
+ text-decoration: none;
+ font-size: 1.25em;
+ font-weight: 700;
+}
+
+.auth-brand .brand-icon {
+ font-size: 1.5em;
+ color: #ff0000;
+}
+
+.auth-card {
+ width: 100%;
+ max-width: 440px;
+ padding: 40px;
+ background: #18191c;
+ border-radius: 8px;
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
+}
+
+.auth-card h1 {
+ margin: 0 0 8px;
+ font-size: 1.75em;
+ color: #fff;
+ text-align: center;
+}
+
+.auth-subtitle {
+ text-align: center;
+ color: #b9bbbe;
+ margin-bottom: 24px;
+}
+
+.auth-error {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 12px;
+ background: rgba(237, 66, 69, 0.1);
+ border: 1px solid #ed4245;
+ border-radius: 4px;
+ color: #ed4245;
+ margin-bottom: 16px;
+}
+
+.auth-form {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+.form-group {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.form-group label {
+ font-size: 0.75em;
+ font-weight: 700;
+ text-transform: uppercase;
+ color: #b9bbbe;
+}
+
+.form-group .required {
+ color: #ed4245;
+}
+
+.form-group input {
+ padding: 12px 14px;
+ background: #0a0a0a;
+ border: 1px solid #2f3136;
+ border-radius: 4px;
+ color: #e0e0e0;
+ font-size: 1em;
+ transition: border-color 0.2s;
+}
+
+.form-group input:focus {
+ outline: none;
+ border-color: #5865f2;
+}
+
+.form-group input:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.forgot-link {
+ font-size: 0.85em;
+ color: #00aff4;
+ text-decoration: none;
+ margin-top: 4px;
+}
+
+.forgot-link:hover {
+ text-decoration: underline;
+}
+
+.auth-submit {
+ padding: 14px;
+ background: #5865f2;
+ border: none;
+ border-radius: 4px;
+ color: #fff;
+ font-size: 1em;
+ font-weight: 600;
+ cursor: pointer;
+ transition: background 0.2s;
+ margin-top: 8px;
+}
+
+.auth-submit:hover {
+ background: #4752c4;
+}
+
+.auth-submit:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.terms-checkbox {
+ display: flex;
+ align-items: flex-start;
+ gap: 10px;
+ font-size: 0.9em;
+ color: #b9bbbe;
+ cursor: pointer;
+}
+
+.terms-checkbox input {
+ width: 18px;
+ height: 18px;
+ accent-color: #5865f2;
+ margin-top: 2px;
+}
+
+.terms-checkbox a {
+ color: #00aff4;
+ text-decoration: none;
+}
+
+.terms-checkbox a:hover {
+ text-decoration: underline;
+}
+
+.auth-switch {
+ text-align: center;
+ margin-top: 16px;
+ color: #b9bbbe;
+ font-size: 0.9em;
+}
+
+.auth-switch a {
+ color: #00aff4;
+ text-decoration: none;
+}
+
+.auth-switch a:hover {
+ text-decoration: underline;
+}
+
+.auth-footer {
+ margin-top: 24px;
+}
+
+.auth-footer a {
+ color: #666;
+ text-decoration: none;
+ font-size: 0.9em;
+}
+
+.auth-footer a:hover {
+ color: #b9bbbe;
+}
+
+.auth-visual {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: linear-gradient(135deg, #0a0a0a, #18191c);
+ position: relative;
+ overflow: hidden;
+}
+
+.visual-content {
+ text-align: center;
+ z-index: 1;
+}
+
+.trinity-logo {
+ width: 200px;
+ height: 200px;
+ position: relative;
+ margin: 0 auto 32px;
+}
+
+.trinity-ring {
+ position: absolute;
+ border-radius: 50%;
+ border: 3px solid;
+ animation: spin 20s linear infinite;
+}
+
+.trinity-ring.foundation {
+ width: 100%;
+ height: 100%;
+ border-color: #ff0000;
+}
+
+.trinity-ring.corporation {
+ width: 75%;
+ height: 75%;
+ top: 12.5%;
+ left: 12.5%;
+ border-color: #0066ff;
+ animation-direction: reverse;
+ animation-duration: 15s;
+}
+
+.trinity-ring.labs {
+ width: 50%;
+ height: 50%;
+ top: 25%;
+ left: 25%;
+ border-color: #ffa500;
+ animation-duration: 10s;
+}
+
+@keyframes spin {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+}
+
+.visual-content h2 {
+ font-size: 2em;
+ margin: 0 0 8px;
+ color: #fff;
+}
+
+.visual-content p {
+ color: #666;
+}
+
+.register-card {
+ max-width: 480px;
+}
+
+/* ==================== INFO PAGES (About, Features) ==================== */
+.info-page {
+ min-height: 100vh;
+ background: #0a0a0a;
+ color: #e0e0e0;
+}
+
+.info-nav {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 20px 60px;
+ background: rgba(10, 10, 10, 0.95);
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
+ position: sticky;
+ top: 0;
+ z-index: 100;
+}
+
+.info-content {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 80px 60px;
+}
+
+.about-hero,
+.features-hero {
+ text-align: center;
+ margin-bottom: 80px;
+}
+
+.about-hero h1,
+.features-hero h1 {
+ font-size: 3em;
+ margin: 0 0 16px;
+ color: #fff;
+}
+
+.hero-desc {
+ font-size: 1.25em;
+ color: #b9bbbe;
+ max-width: 600px;
+ margin: 0 auto;
+}
+
+.about-section {
+ margin-bottom: 80px;
+}
+
+.about-section h2 {
+ font-size: 2em;
+ margin: 0 0 24px;
+ color: #fff;
+}
+
+.section-intro {
+ color: #b9bbbe;
+ margin-bottom: 32px;
+}
+
+.mission-content {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 40px;
+}
+
+.mission-text p {
+ margin: 0 0 16px;
+ line-height: 1.7;
+ color: #b9bbbe;
+}
+
+.mission-text strong {
+ color: #fff;
+}
+
+.mission-values {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+}
+
+.value-card {
+ padding: 24px;
+ background: #18191c;
+ border-radius: 8px;
+ border: 1px solid #2f3136;
+}
+
+.value-icon {
+ font-size: 2em;
+ margin-bottom: 12px;
+ display: block;
+}
+
+.value-card h3 {
+ margin: 0 0 8px;
+ color: #fff;
+}
+
+.value-card p {
+ margin: 0;
+ color: #666;
+ font-size: 0.9em;
+}
+
+.trinity-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 24px;
+}
+
+.trinity-card {
+ padding: 32px;
+ background: #18191c;
+ border-radius: 12px;
+ border: 2px solid #2f3136;
+ text-align: center;
+ transition: all 0.3s;
+}
+
+.trinity-card.foundation {
+ border-color: #ff0000;
+}
+
+.trinity-card.corporation {
+ border-color: #0066ff;
+}
+
+.trinity-card.labs {
+ border-color: #ffa500;
+}
+
+.trinity-card:hover {
+ transform: translateY(-4px);
+}
+
+.trinity-icon {
+ font-size: 3em;
+ margin-bottom: 16px;
+}
+
+.trinity-card h3 {
+ margin: 0 0 8px;
+ color: #fff;
+ font-size: 1.5em;
+}
+
+.trinity-card p {
+ margin: 0;
+ color: #b9bbbe;
+}
+
+.timeline {
+ position: relative;
+ padding-left: 40px;
+}
+
+.timeline::before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ width: 2px;
+ background: linear-gradient(to bottom, #ff0000, #0066ff, #ffa500);
+}
+
+.timeline-item {
+ position: relative;
+ padding-bottom: 40px;
+}
+
+.timeline-item::before {
+ content: '';
+ position: absolute;
+ left: -44px;
+ top: 4px;
+ width: 12px;
+ height: 12px;
+ background: #5865f2;
+ border-radius: 50%;
+ border: 3px solid #0a0a0a;
+}
+
+.timeline-year {
+ font-size: 0.85em;
+ font-weight: 600;
+ color: #5865f2;
+ margin-bottom: 8px;
+}
+
+.timeline-content h3 {
+ margin: 0 0 8px;
+ color: #fff;
+}
+
+.timeline-content p {
+ margin: 0;
+ color: #b9bbbe;
+}
+
+.team-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+ gap: 24px;
+}
+
+.team-card {
+ padding: 32px;
+ background: #18191c;
+ border-radius: 8px;
+ text-align: center;
+}
+
+.team-avatar {
+ width: 80px;
+ height: 80px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 2em;
+ font-weight: 700;
+ margin: 0 auto 16px;
+ color: #fff;
+}
+
+.team-card h3 {
+ margin: 0 0 4px;
+ color: #fff;
+}
+
+.team-card p {
+ margin: 0;
+ color: #666;
+ font-size: 0.9em;
+}
+
+.about-cta,
+.features-cta {
+ text-align: center;
+ padding: 60px;
+ background: #18191c;
+ border-radius: 12px;
+ margin-top: 60px;
+}
+
+.about-cta h2,
+.features-cta h2 {
+ margin: 0 0 12px;
+ font-size: 2em;
+ color: #fff;
+}
+
+.about-cta p,
+.features-cta p {
+ margin: 0 0 32px;
+ color: #b9bbbe;
+}
+
+.cta-actions {
+ display: flex;
+ gap: 16px;
+ justify-content: center;
+}
+
+.feature-category {
+ margin-bottom: 60px;
+}
+
+.feature-category h2 {
+ font-size: 1.75em;
+ margin: 0 0 24px;
+ color: #fff;
+}
+
+.comparison-section {
+ margin-top: 80px;
+}
+
+.comparison-section h2 {
+ font-size: 2em;
+ text-align: center;
+ margin: 0 0 40px;
+ color: #fff;
+}
+
+.comparison-table-container {
+ overflow-x: auto;
+}
+
+.comparison-table {
+ width: 100%;
+ border-collapse: collapse;
+ background: #18191c;
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.comparison-table th,
+.comparison-table td {
+ padding: 16px 24px;
+ text-align: center;
+ border-bottom: 1px solid #2f3136;
+}
+
+.comparison-table th {
+ background: #0f0f0f;
+ font-weight: 600;
+ color: #b9bbbe;
+}
+
+.comparison-table th.highlight {
+ color: #5865f2;
+}
+
+.comparison-table td {
+ color: #666;
+}
+
+.comparison-table td.highlight {
+ color: #3ba55d;
+ font-weight: 600;
+}
+
+.comparison-table td:first-child {
+ text-align: left;
+ font-weight: 500;
+ color: #e0e0e0;
+}
+
+.info-footer {
+ border-top: 1px solid #2f3136;
+ padding: 40px 60px;
+}
+
+.footer-content {
+ max-width: 1200px;
+ margin: 0 auto;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.footer-brand {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-weight: 600;
+ color: #fff;
+}
+
+.footer-brand .brand-icon {
+ color: #ff0000;
+}
+
+.footer-links {
+ display: flex;
+ gap: 24px;
+}
+
+.footer-links a {
+ color: #b9bbbe;
+ text-decoration: none;
+ font-size: 0.9em;
+}
+
+.footer-links a:hover {
+ color: #fff;
+}
+
+.footer-copy {
+ color: #666;
+ font-size: 0.85em;
+}
+
+/* Loading screen */
+.loading-screen {
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ background: #0a0a0a;
+ color: #b9bbbe;
+ gap: 16px;
+}
+
+.loading-spinner {
+ width: 40px;
+ height: 40px;
+ border: 3px solid #2f3136;
+ border-top-color: #5865f2;
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+}
+
+/* Responsive */
+@media (max-width: 1024px) {
+ .hero {
+ flex-direction: column;
+ text-align: center;
+ padding-top: 140px;
+ }
+
+ .hero-title {
+ font-size: 3em;
+ }
+
+ .hero-actions {
+ justify-content: center;
+ }
+
+ .hero-stats {
+ justify-content: center;
+ }
+
+ .hero-visual {
+ display: none;
+ }
+
+ .features-grid {
+ grid-template-columns: repeat(2, 1fr);
+ }
+
+ .mission-content {
+ grid-template-columns: 1fr;
+ }
+
+ .trinity-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .auth-visual {
+ display: none;
+ }
+}
+
+@media (max-width: 768px) {
+ .landing-nav,
+ .info-nav {
+ padding: 16px 20px;
+ }
+
+ .nav-links {
+ display: none;
+ }
+
+ .hero {
+ padding: 100px 20px 40px;
+ }
+
+ .hero-title {
+ font-size: 2em;
+ }
+
+ .hero-subtitle {
+ font-size: 1em;
+ }
+
+ .hero-actions {
+ flex-direction: column;
+ }
+
+ .features-section,
+ .info-content {
+ padding: 40px 20px;
+ }
+
+ .features-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .auth-container {
+ padding: 20px;
+ }
+
+ .auth-card {
+ padding: 24px;
+ }
+
+ .footer-content {
+ flex-direction: column;
+ gap: 20px;
+ text-align: center;
+ }
+}
diff --git a/src/frontend/mockup/pages/AboutPage.jsx b/src/frontend/mockup/pages/AboutPage.jsx
new file mode 100644
index 0000000..885e0e9
--- /dev/null
+++ b/src/frontend/mockup/pages/AboutPage.jsx
@@ -0,0 +1,163 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+
+export default function AboutPage() {
+ const team = [
+ { name: 'Trevor', role: 'Founder & Lead Developer', avatar: 'T', color: '#ff0000' },
+ { name: 'Open Source', role: 'Community Contributors', avatar: '๐ฅ', color: '#5865f2' },
+ ];
+
+ const timeline = [
+ { year: '2024', event: 'AeThex Foundation established', desc: 'Core infrastructure and passport system development begins' },
+ { year: '2025', event: 'AeThex Connect launched', desc: 'Open source Discord alternative enters alpha' },
+ { year: '2026', event: 'Public beta release', desc: 'Full feature parity achieved, community growth accelerates' },
+ ];
+
+ return (
+
+
+
+ โ
+ AeThex Connect
+
+
+
+ Login
+ Get Started
+
+
+
+
+
+ About AeThex
+
+ Building the future of decentralized communication, one line of code at a time.
+
+
+
+
+ Our Mission
+
+
+
+ AeThex was founded on a simple belief: your data belongs to you .
+ In a world where major platforms profit from your conversations, we chose a different path.
+
+
+ AeThex Connect is our answer to the centralized communication monopoly.
+ It's not just an alternative to Discordโit's a statement that privacy, security,
+ and user ownership can coexist with a world-class communication experience.
+
+
+ As an open-source project, every line of our code is public.
+ We believe transparency isn't just nice to haveโit's essential for trust.
+
+
+
+
+
๐
+
Privacy First
+
End-to-end encryption by default. We can't read your messages even if we wanted to.
+
+
+
๐
+
Open Source
+
100% of our code is public. Audit it, fork it, contribute to it.
+
+
+
โก
+
User Owned
+
Your identity, your data, your control. Forever.
+
+
+
+
+
+
+ The Trinity
+ AeThex operates as three interconnected divisions
+
+
+
๐๏ธ
+
Foundation
+
The core. Authentication, security infrastructure, and the Passport identity system.
+
+
+
๐ข
+
Corporation
+
Enterprise solutions, business integrations, and commercial support services.
+
+
+
๐ฌ
+
Labs
+
Experimental features, R&D, and cutting-edge technology development.
+
+
+
+
+
+ Our Journey
+
+ {timeline.map((item, i) => (
+
+
{item.year}
+
+
{item.event}
+
{item.desc}
+
+
+ ))}
+
+
+
+
+ The Team
+
+ {team.map((member, i) => (
+
+
+ {member.avatar}
+
+
{member.name}
+
{member.role}
+
+ ))}
+
+
+
+
+ Ready to join the revolution?
+ Be part of the community shaping the future of communication.
+
+
+
+
+
+
+ );
+}
diff --git a/src/frontend/mockup/pages/FeaturesPage.jsx b/src/frontend/mockup/pages/FeaturesPage.jsx
new file mode 100644
index 0000000..64fcf3f
--- /dev/null
+++ b/src/frontend/mockup/pages/FeaturesPage.jsx
@@ -0,0 +1,159 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+
+export default function FeaturesPage() {
+ const featureCategories = [
+ {
+ title: 'Communication',
+ features: [
+ { icon: '๐ฌ', name: 'Real-time Messaging', desc: 'Instant message delivery with typing indicators, reactions, and rich embeds' },
+ { icon: '๐๏ธ', name: 'Voice Channels', desc: 'Crystal-clear voice chat with noise suppression and echo cancellation' },
+ { icon: '๐น', name: 'Video Calls', desc: 'HD video calls with screen sharing and virtual backgrounds' },
+ { icon: '๐งต', name: 'Threads', desc: 'Organized conversations that keep your channels clean' },
+ { icon: '๐', name: 'Pinned Messages', desc: 'Save important messages for easy access' },
+ { icon: '๐', name: 'Powerful Search', desc: 'Find any message, file, or conversation instantly' },
+ ]
+ },
+ {
+ title: 'Community',
+ features: [
+ { icon: '๐ ', name: 'Servers', desc: 'Create communities with unlimited channels and categories' },
+ { icon: '๐ญ', name: 'Roles & Permissions', desc: 'Granular control over who can do what' },
+ { icon: '๐ข', name: 'Announcements', desc: 'Broadcast to your community with announcement channels' },
+ { icon: '๐
', name: 'Events', desc: 'Schedule and manage community events' },
+ { icon: '๐ช', name: 'Stage Channels', desc: 'Host live audio events for large audiences' },
+ { icon: '๐ฌ', name: 'Forum Channels', desc: 'Organized discussion boards for your community' },
+ ]
+ },
+ {
+ title: 'Security & Privacy',
+ features: [
+ { icon: '๐', name: 'End-to-End Encryption', desc: 'Your messages are encrypted and only you can read them' },
+ { icon: '๐ก๏ธ', name: 'AeThex Passport', desc: 'Secure, decentralized identity that you own' },
+ { icon: '๐', name: 'Two-Factor Auth', desc: 'Extra security for your account' },
+ { icon: '๐ป', name: 'Ephemeral Messages', desc: 'Self-destructing messages for sensitive conversations' },
+ { icon: '๐ซ', name: 'No Data Selling', desc: 'Your data is never sold to third parties. Ever.' },
+ { icon: '๐', name: 'Audit Logs', desc: 'Full transparency on server actions' },
+ ]
+ },
+ {
+ title: 'Customization',
+ features: [
+ { icon: '๐จ', name: 'Themes', desc: 'Dark, light, and custom themes to match your style' },
+ { icon: '๐', name: 'Custom Emoji', desc: 'Upload your own emoji and stickers' },
+ { icon: '๐ต', name: 'Soundboard', desc: 'Play sound effects in voice channels' },
+ { icon: '๐ค', name: 'Bots & Integrations', desc: 'Extend functionality with custom bots' },
+ { icon: 'โ๏ธ', name: 'Webhooks', desc: 'Connect external services to your channels' },
+ { icon: '๐', name: 'Server Insights', desc: 'Analytics to understand your community' },
+ ]
+ },
+ ];
+
+ const comparisons = [
+ { feature: 'Open Source', aethex: true, discord: false, slack: false },
+ { feature: 'E2E Encryption', aethex: true, discord: false, slack: false },
+ { feature: 'Self-Hostable', aethex: true, discord: false, slack: false },
+ { feature: 'No Data Collection', aethex: true, discord: false, slack: false },
+ { feature: 'Voice/Video', aethex: true, discord: true, slack: true },
+ { feature: 'Unlimited History', aethex: true, discord: true, slack: false },
+ { feature: 'Custom Bots', aethex: true, discord: true, slack: true },
+ { feature: 'Free to Use', aethex: true, discord: true, slack: false },
+ ];
+
+ return (
+
+
+
+ โ
+ AeThex Connect
+
+
+
+ Login
+ Get Started
+
+
+
+
+
+ Everything you need,nothing you don't
+
+ All the features you love from Discord, with the privacy and ownership you deserve.
+
+
+
+ {featureCategories.map((category, idx) => (
+
+ {category.title}
+
+ {category.features.map((feature, i) => (
+
+
{feature.icon}
+
{feature.name}
+
{feature.desc}
+
+ ))}
+
+
+ ))}
+
+
+ How We Compare
+
+
+
+
+ Feature
+ AeThex Connect
+ Discord
+ Slack
+
+
+
+ {comparisons.map((row, i) => (
+
+ {row.feature}
+ {row.aethex ? 'โ' : 'โ'}
+ {row.discord ? 'โ' : 'โ'}
+ {row.slack ? 'โ' : 'โ'}
+
+ ))}
+
+
+
+
+
+
+ Ready to experience the difference?
+ Join thousands of users who've made the switch.
+
+ Get Started Free
+ Already have an account?
+
+
+
+
+
+
+ );
+}
diff --git a/src/frontend/mockup/pages/LandingPage.jsx b/src/frontend/mockup/pages/LandingPage.jsx
new file mode 100644
index 0000000..d6b84c0
--- /dev/null
+++ b/src/frontend/mockup/pages/LandingPage.jsx
@@ -0,0 +1,202 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+
+export default function LandingPage() {
+ return (
+
+ {/* Navigation */}
+
+
+ โ
+ AeThex Connect
+
+
+
+ Login
+ Get Started
+
+
+
+ {/* Hero Section */}
+
+
+
+
+ Open Source โข Privacy-First โข Decentralized
+
+
+ Connect Without
+
+ Compromise
+
+
+ The next-generation communication platform built for gamers, developers,
+ and communities who value privacy, security, and true ownership of their data.
+
+
+
+
+ 100%
+ Open Source
+
+
+
+ E2E
+ Encrypted
+
+
+
+ 0
+ Data Sold
+
+
+
+
+
+
+ {/* Features Section */}
+
+ Why AeThex Connect?
+
+ Built from the ground up with the features that matter most
+
+
+
+
๐
+
End-to-End Encryption
+
Every message, call, and file is encrypted. Only you and your recipients can read them.
+
+
+
๐ฎ
+
GameForge Integration
+
Rich presence, game invites, and overlay support for your favorite games.
+
+
+
๐
+
Crystal Clear Calls
+
HD voice and video calls with screen sharing, powered by WebRTC.
+
+
+
๐
+
Cross-Platform
+
Web, Desktop, iOS, and Android. Your conversations sync everywhere.
+
+
+
๐
+
Open Source
+
Fully auditable code. No hidden backdoors. Community-driven development.
+
+
+
โก
+
Blazing Fast
+
Optimized for performance. No bloat, no lag, just pure speed.
+
+
+
+
+ {/* Trinity Section */}
+
+ The AeThex Trinity
+ Three divisions, one ecosystem
+
+
+
โ
+
Foundation
+
Core security, authentication, and identity. The bedrock of trust.
+
+
+
โ
+
Corporation
+
Enterprise solutions and professional services. Built for scale.
+
+
+
โ
+
Labs
+
Research and experimental features. Tomorrow's technology today.
+
+
+
+
+ {/* CTA Section */}
+
+
+
Ready to Connect?
+
Join the community that values your privacy as much as you do.
+
+ Create Your Account
+
+
+
+
+ {/* Footer */}
+
+
+ );
+}
diff --git a/src/frontend/mockup/pages/LoginPage.jsx b/src/frontend/mockup/pages/LoginPage.jsx
new file mode 100644
index 0000000..223428a
--- /dev/null
+++ b/src/frontend/mockup/pages/LoginPage.jsx
@@ -0,0 +1,107 @@
+import React, { useState } from 'react';
+import { Link, useNavigate } from 'react-router-dom';
+
+export default function LoginPage({ onLogin }) {
+ const navigate = useNavigate();
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [error, setError] = useState('');
+ const [loading, setLoading] = useState(false);
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ setError('');
+ setLoading(true);
+
+ try {
+ // Mock login - replace with Supabase auth
+ if (email && password) {
+ // Simulate API call
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ // For demo, accept any valid-looking email
+ if (email.includes('@') && password.length >= 6) {
+ onLogin?.({ email });
+ navigate('/app');
+ } else {
+ setError('Invalid email or password');
+ }
+ }
+ } catch (err) {
+ setError('An error occurred. Please try again.');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+
+
+
+ โ
+ AeThex Connect
+
+
+
+
+
Welcome back!
+
We're so excited to see you again!
+
+ {error && (
+
+ โ ๏ธ
+ {error}
+
+ )}
+
+
+
+
+ Need an account? Register
+
+
+
+
+ โ Back to home
+
+
+
+
+
+ );
+}
diff --git a/src/frontend/mockup/pages/RegisterPage.jsx b/src/frontend/mockup/pages/RegisterPage.jsx
new file mode 100644
index 0000000..8826442
--- /dev/null
+++ b/src/frontend/mockup/pages/RegisterPage.jsx
@@ -0,0 +1,183 @@
+import React, { useState } from 'react';
+import { Link, useNavigate } from 'react-router-dom';
+
+export default function RegisterPage({ onRegister }) {
+ const navigate = useNavigate();
+ const [formData, setFormData] = useState({
+ email: '',
+ username: '',
+ displayName: '',
+ password: '',
+ confirmPassword: '',
+ });
+ const [error, setError] = useState('');
+ const [loading, setLoading] = useState(false);
+ const [agreedToTerms, setAgreedToTerms] = useState(false);
+
+ const handleChange = (e) => {
+ setFormData(prev => ({ ...prev, [e.target.name]: e.target.value }));
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ setError('');
+
+ if (formData.password !== formData.confirmPassword) {
+ setError('Passwords do not match');
+ return;
+ }
+
+ if (formData.password.length < 8) {
+ setError('Password must be at least 8 characters');
+ return;
+ }
+
+ if (!agreedToTerms) {
+ setError('You must agree to the Terms of Service');
+ return;
+ }
+
+ setLoading(true);
+
+ try {
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ onRegister?.({ email: formData.email, username: formData.username });
+ navigate('/app');
+ } catch (err) {
+ setError('Registration failed. Please try again.');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+
+
+
+ โ
+ AeThex Connect
+
+
+
+
+
Create an account
+
Join the future of communication
+
+ {error && (
+
+ โ ๏ธ
+ {error}
+
+ )}
+
+
+
+
+ Already have an account? Log In
+
+
+
+
+ โ Back to home
+
+
+
+
+
+
+
The Trinity Awaits
+
Foundation โข Corporation โข Labs
+
+
+
+ );
+}
diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json
index f434479..1cc5bc6 100644
--- a/src/frontend/package-lock.json
+++ b/src/frontend/package-lock.json
@@ -10,6 +10,8 @@
"dependencies": {
"@stripe/react-stripe-js": "^5.4.1",
"@stripe/stripe-js": "^8.6.1",
+ "@supabase/supabase-js": "^2.94.1",
+ "@tailwindcss/postcss": "^4.1.18",
"axios": "^1.13.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@@ -20,9 +22,24 @@
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"@vitejs/plugin-react": "^4.2.1",
+ "autoprefixer": "^10.4.24",
+ "postcss": "^8.5.6",
+ "tailwindcss": "^4.1.18",
"vite": "^5.0.8"
}
},
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/@babel/code-frame": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
@@ -701,7 +718,6 @@
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
@@ -712,7 +728,6 @@
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
@@ -723,7 +738,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
@@ -733,14 +747,12 @@
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
- "dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@@ -1143,6 +1155,342 @@
"node": ">=12.16"
}
},
+ "node_modules/@supabase/auth-js": {
+ "version": "2.94.1",
+ "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.94.1.tgz",
+ "integrity": "sha512-Wt/SdmAtNNiqrcBbPlzWojLcE1bQ9OYb8PTaYF6QccFX5JeXZI0sZ01MLNE+E83UK6cK0lw4YznX0D2g08UQng==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "2.8.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@supabase/functions-js": {
+ "version": "2.94.1",
+ "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.94.1.tgz",
+ "integrity": "sha512-A7Bx0gnclDNZ4m8+mnO2IEEzMxtUSg7cpPEBF6Ek1LpjIQkC7vvoidiV/RuntnKX43IiVcWV1f2FsAppMagEmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "2.8.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@supabase/postgrest-js": {
+ "version": "2.94.1",
+ "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.94.1.tgz",
+ "integrity": "sha512-N6MTghjHnMZddT48rAj8dIFgedCU97cc1ahQM74Tc+DF4UH7y2+iEfdYV3unJsylpaiWlu92Fy8Lj14Jbrmxog==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "2.8.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@supabase/realtime-js": {
+ "version": "2.94.1",
+ "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.94.1.tgz",
+ "integrity": "sha512-Wq8olpCAGmN4y2DH2kUdlcakdzNHRCde72BFS8zK5ub46bBeSUoE9DqrfeNFWKaF2gCE/cmK8aTUTorZD9jdtQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/phoenix": "^1.6.6",
+ "@types/ws": "^8.18.1",
+ "tslib": "2.8.1",
+ "ws": "^8.18.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@supabase/storage-js": {
+ "version": "2.94.1",
+ "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.94.1.tgz",
+ "integrity": "sha512-/Mi18LGyrugPwtfqETfAqEGcBQotY/7IMsTGYgEFdqr8cQq280BVQWjN2wI9KibWtshPp0Ryvil5Uzd5YfM7kA==",
+ "license": "MIT",
+ "dependencies": {
+ "iceberg-js": "^0.8.1",
+ "tslib": "2.8.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@supabase/supabase-js": {
+ "version": "2.94.1",
+ "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.94.1.tgz",
+ "integrity": "sha512-87vOY8n3WHB3m+a/KeySj07djOQVuRA5qgX5E7db1eDkaZ1of5M+3t/tv6eYYy4BfqxuHMZuCe5uVrO/oyvoow==",
+ "license": "MIT",
+ "dependencies": {
+ "@supabase/auth-js": "2.94.1",
+ "@supabase/functions-js": "2.94.1",
+ "@supabase/postgrest-js": "2.94.1",
+ "@supabase/realtime-js": "2.94.1",
+ "@supabase/storage-js": "2.94.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz",
+ "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.4",
+ "enhanced-resolve": "^5.18.3",
+ "jiti": "^2.6.1",
+ "lightningcss": "1.30.2",
+ "magic-string": "^0.30.21",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.1.18"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz",
+ "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.1.18",
+ "@tailwindcss/oxide-darwin-arm64": "4.1.18",
+ "@tailwindcss/oxide-darwin-x64": "4.1.18",
+ "@tailwindcss/oxide-freebsd-x64": "4.1.18",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.1.18",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.1.18",
+ "@tailwindcss/oxide-linux-x64-musl": "4.1.18",
+ "@tailwindcss/oxide-wasm32-wasi": "4.1.18",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.1.18"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz",
+ "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz",
+ "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz",
+ "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz",
+ "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz",
+ "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz",
+ "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz",
+ "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz",
+ "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz",
+ "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz",
+ "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1",
+ "@emnapi/wasi-threads": "^1.1.0",
+ "@napi-rs/wasm-runtime": "^1.1.0",
+ "@tybys/wasm-util": "^0.10.1",
+ "tslib": "^2.4.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz",
+ "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz",
+ "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/postcss": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.18.tgz",
+ "integrity": "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==",
+ "license": "MIT",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "@tailwindcss/node": "4.1.18",
+ "@tailwindcss/oxide": "4.1.18",
+ "postcss": "^8.4.41",
+ "tailwindcss": "4.1.18"
+ }
+ },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -1195,6 +1543,21 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/node": {
+ "version": "25.2.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.0.tgz",
+ "integrity": "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.16.0"
+ }
+ },
+ "node_modules/@types/phoenix": {
+ "version": "1.6.7",
+ "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.7.tgz",
+ "integrity": "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==",
+ "license": "MIT"
+ },
"node_modules/@types/prop-types": {
"version": "15.7.15",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
@@ -1224,6 +1587,15 @@
"@types/react": "^18.0.0"
}
},
+ "node_modules/@types/ws": {
+ "version": "8.18.1",
+ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
+ "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@vitejs/plugin-react": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
@@ -1251,6 +1623,43 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
+ "node_modules/autoprefixer": {
+ "version": "10.4.24",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.24.tgz",
+ "integrity": "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.28.1",
+ "caniuse-lite": "^1.0.30001766",
+ "fraction.js": "^5.3.4",
+ "picocolors": "^1.1.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
"node_modules/axios": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
@@ -1321,9 +1730,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001763",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001763.tgz",
- "integrity": "sha512-mh/dGtq56uN98LlNX9qdbKnzINhX0QzhiWBFEkFfsFO4QyCvL8YegrJAazCwXIeqkIob8BlZPGM3xdnY+sgmvQ==",
+ "version": "1.0.30001768",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001768.tgz",
+ "integrity": "sha512-qY3aDRZC5nWPgHUgIB84WL+nySuo19wk0VJpp/XI9T34lrvkyhRvNVOFJOp2kxClQhiFBu+TaUSudf6oa3vkSA==",
"dev": true,
"funding": [
{
@@ -1393,6 +1802,15 @@
"node": ">=0.4.0"
}
},
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -1436,6 +1854,19 @@
"node": ">=10.0.0"
}
},
+ "node_modules/enhanced-resolve": {
+ "version": "5.19.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz",
+ "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==",
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.3.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@@ -1566,6 +1997,20 @@
"node": ">= 6"
}
},
+ "node_modules/fraction.js": {
+ "version": "5.3.4",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
+ "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -1649,6 +2094,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "license": "ISC"
+ },
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
@@ -1688,6 +2139,24 @@
"node": ">= 0.4"
}
},
+ "node_modules/iceberg-js": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz",
+ "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -1720,6 +2189,255 @@
"node": ">=6"
}
},
+ "node_modules/lightningcss": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
+ "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.30.2",
+ "lightningcss-darwin-arm64": "1.30.2",
+ "lightningcss-darwin-x64": "1.30.2",
+ "lightningcss-freebsd-x64": "1.30.2",
+ "lightningcss-linux-arm-gnueabihf": "1.30.2",
+ "lightningcss-linux-arm64-gnu": "1.30.2",
+ "lightningcss-linux-arm64-musl": "1.30.2",
+ "lightningcss-linux-x64-gnu": "1.30.2",
+ "lightningcss-linux-x64-musl": "1.30.2",
+ "lightningcss-win32-arm64-msvc": "1.30.2",
+ "lightningcss-win32-x64-msvc": "1.30.2"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz",
+ "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz",
+ "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz",
+ "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz",
+ "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz",
+ "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz",
+ "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz",
+ "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
+ "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz",
+ "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz",
+ "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz",
+ "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -1742,6 +2460,15 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -1782,7 +2509,6 @@
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -1817,14 +2543,12 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "dev": true,
"license": "ISC"
},
"node_modules/postcss": {
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
- "dev": true,
"funding": [
{
"type": "opencollective",
@@ -1840,6 +2564,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -1849,6 +2574,13 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -2037,12 +2769,42 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
+ "node_modules/tailwindcss": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
+ "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
+ "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/undici-types": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
+ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
+ "license": "MIT"
+ },
"node_modules/update-browserslist-db": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
diff --git a/src/frontend/package.json b/src/frontend/package.json
index 57dfcee..c4ba0dd 100644
--- a/src/frontend/package.json
+++ b/src/frontend/package.json
@@ -11,6 +11,8 @@
"dependencies": {
"@stripe/react-stripe-js": "^5.4.1",
"@stripe/stripe-js": "^8.6.1",
+ "@supabase/supabase-js": "^2.94.1",
+ "@tailwindcss/postcss": "^4.1.18",
"axios": "^1.13.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@@ -21,6 +23,9 @@
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"@vitejs/plugin-react": "^4.2.1",
+ "autoprefixer": "^10.4.24",
+ "postcss": "^8.5.6",
+ "tailwindcss": "^4.1.18",
"vite": "^5.0.8"
}
}
diff --git a/src/frontend/postcss.config.js b/src/frontend/postcss.config.js
new file mode 100644
index 0000000..1c87846
--- /dev/null
+++ b/src/frontend/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ '@tailwindcss/postcss': {},
+ autoprefixer: {},
+ },
+}
diff --git a/src/frontend/services/messaging.js b/src/frontend/services/messaging.js
new file mode 100644
index 0000000..82ade60
--- /dev/null
+++ b/src/frontend/services/messaging.js
@@ -0,0 +1,159 @@
+import { createClient } from '@supabase/supabase-js';
+import { io } from 'socket.io-client';
+
+// Supabase client
+const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || 'http://127.0.0.1:3000';
+const supabaseKey = import.meta.env.VITE_SUPABASE_ANON_KEY || 'sb_publishable_ACJWlzQHlZjBrEguHvfOxg_3BJgxAaH';
+
+export const supabase = createClient(supabaseUrl, supabaseKey);
+
+// Socket.IO client
+const socketUrl = import.meta.env.VITE_SOCKET_URL || 'http://localhost:3000';
+export const socket = io(socketUrl, {
+ autoConnect: false,
+ transports: ['websocket', 'polling'],
+});
+
+// Connect socket when user is authenticated
+export const connectSocket = (userId) => {
+ if (!socket.connected) {
+ socket.auth = { userId };
+ socket.connect();
+ }
+};
+
+export const disconnectSocket = () => {
+ if (socket.connected) {
+ socket.disconnect();
+ }
+};
+
+// Messaging API
+export const messagingService = {
+ // Get all conversations for a user
+ async getConversations(userId) {
+ const { data, error } = await supabase
+ .from('conversation_participants')
+ .select(`
+ conversation_id,
+ conversations (
+ id,
+ type,
+ title,
+ description,
+ avatar_url,
+ created_at,
+ updated_at
+ )
+ `)
+ .eq('user_id', userId)
+ .order('conversations(updated_at)', { ascending: false });
+
+ if (error) throw error;
+ return data?.map(p => p.conversations) || [];
+ },
+
+ // Get messages for a conversation
+ async getMessages(conversationId, limit = 50) {
+ const { data, error } = await supabase
+ .from('messages')
+ .select('*')
+ .eq('conversation_id', conversationId)
+ .order('created_at', { ascending: false })
+ .limit(limit);
+
+ if (error) throw error;
+ return (data || []).reverse();
+ },
+
+ // Send a message
+ async sendMessage(conversationId, senderId, content) {
+ const { data, error } = await supabase
+ .from('messages')
+ .insert({
+ conversation_id: conversationId,
+ sender_id: senderId,
+ content_encrypted: content, // TODO: Add actual encryption
+ content_type: 'text',
+ })
+ .select()
+ .single();
+
+ if (error) throw error;
+
+ // Emit via socket for real-time
+ socket.emit('message:send', {
+ conversationId,
+ message: data,
+ });
+
+ return data;
+ },
+
+ // Create a new conversation
+ async createConversation(type, title, participantIds) {
+ const { data: conversation, error: convError } = await supabase
+ .from('conversations')
+ .insert({ type, title })
+ .select()
+ .single();
+
+ if (convError) throw convError;
+
+ // Add participants
+ const participants = participantIds.map(userId => ({
+ conversation_id: conversation.id,
+ user_id: userId,
+ role: 'member',
+ }));
+
+ const { error: partError } = await supabase
+ .from('conversation_participants')
+ .insert(participants);
+
+ if (partError) throw partError;
+
+ return conversation;
+ },
+
+ // Subscribe to new messages in a conversation
+ subscribeToMessages(conversationId, callback) {
+ const channel = supabase
+ .channel(`messages:${conversationId}`)
+ .on(
+ 'postgres_changes',
+ {
+ event: 'INSERT',
+ schema: 'public',
+ table: 'messages',
+ filter: `conversation_id=eq.${conversationId}`,
+ },
+ (payload) => {
+ callback(payload.new);
+ }
+ )
+ .subscribe();
+
+ return () => {
+ supabase.removeChannel(channel);
+ };
+ },
+};
+
+// Socket event handlers
+socket.on('connect', () => {
+ console.log('Socket connected:', socket.id);
+});
+
+socket.on('disconnect', () => {
+ console.log('Socket disconnected');
+});
+
+socket.on('message:new', (message) => {
+ console.log('New message received:', message);
+ // This will be handled by the React context
+});
+
+socket.on('error', (error) => {
+ console.error('Socket error:', error);
+});
diff --git a/src/frontend/tailwind.config.js b/src/frontend/tailwind.config.js
new file mode 100644
index 0000000..547da35
--- /dev/null
+++ b/src/frontend/tailwind.config.js
@@ -0,0 +1,22 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: [
+ "./index.html",
+ "./**/*.{js,ts,jsx,tsx}",
+ ],
+ theme: {
+ extend: {
+ colors: {
+ 'discord-dark': '#0a0a0a',
+ 'discord-darker': '#050505',
+ 'discord-sidebar': '#0d0d0d',
+ 'discord-channel': '#111111',
+ 'discord-hover': '#1a1a1a',
+ 'foundation': '#fbbf24',
+ 'corporation': '#06b6d4',
+ 'labs': '#a855f7',
+ }
+ },
+ },
+ plugins: [],
+}