Merge pull request #1 from AeThex-Corporation/copilot/agreeable-beetle

[WIP] Implement advanced voice, chat, and UX features for AeThex Connect
This commit is contained in:
Anderson 2026-01-18 19:52:03 -07:00 committed by GitHub
commit 5d43b21fce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 3572 additions and 64 deletions

784
aethex-connect-mockup.html Normal file
View file

@ -0,0 +1,784 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AeThex Connect - Metaverse Communication</title>
<style>
@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 */
body::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;
}
/* Server Sidebar */
.server-list {
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;
}
.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;
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;
}
.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;
}
.message-header {
display: flex;
align-items: baseline;
gap: 12px;
margin-bottom: 4px;
}
.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.labs {
background: rgba(255, 165, 0, 0.2);
color: #ffa500;
}
.message-text {
line-height: 1.6;
color: #ccc;
}
.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;
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;
}
</style>
</head>
<body>
<div class="connect-container">
<!-- Server List -->
<div class="server-list">
<div class="server-icon foundation active">F</div>
<div class="server-icon corporation">C</div>
<div class="server-icon labs">L</div>
<div class="server-divider"></div>
<div class="server-icon community">AG</div>
<div class="server-icon community">RD</div>
<div class="server-icon community">+</div>
</div>
<!-- Channel Sidebar -->
<div class="channel-sidebar">
<div class="server-header">
<span>AeThex Foundation</span>
<span class="server-badge foundation">Official</span>
</div>
<div class="channel-list">
<div class="channel-category">Announcements</div>
<div class="channel-item">
<span class="channel-icon">📢</span>
<span class="channel-name">updates</span>
<span class="channel-badge">3</span>
</div>
<div class="channel-item">
<span class="channel-icon">📜</span>
<span class="channel-name">changelog</span>
</div>
<div class="channel-category">Development</div>
<div class="channel-item active">
<span class="channel-icon">#</span>
<span class="channel-name">general</span>
</div>
<div class="channel-item">
<span class="channel-icon">#</span>
<span class="channel-name">api-discussion</span>
</div>
<div class="channel-item">
<span class="channel-icon">#</span>
<span class="channel-name">passport-development</span>
</div>
<div class="channel-category">Support</div>
<div class="channel-item">
<span class="channel-icon"></span>
<span class="channel-name">help</span>
</div>
<div class="channel-item">
<span class="channel-icon">🐛</span>
<span class="channel-name">bug-reports</span>
</div>
<div class="channel-category">Voice Channels</div>
<div class="channel-item">
<span class="channel-icon">🔊</span>
<span class="channel-name">Nexus Lounge</span>
<span style="color: #666; font-size: 0.8em;">3</span>
</div>
</div>
<div class="user-presence">
<div class="user-avatar">A</div>
<div class="user-info">
<div class="user-name">Anderson</div>
<div class="user-status">
<span class="status-dot"></span>
<span>Building AeThex</span>
</div>
</div>
</div>
</div>
<!-- Chat Area -->
<div class="chat-area">
<div class="chat-header">
<span class="channel-name-header"># general</span>
<div class="chat-tools">
<span class="chat-tool">🔔</span>
<span class="chat-tool">📌</span>
<span class="chat-tool">👥 128</span>
<span class="chat-tool">🔍</span>
</div>
</div>
<div class="chat-messages">
<div class="message-system foundation">
<div class="system-label foundation">[FOUNDATION] System Announcement</div>
<div>Foundation authentication services upgraded to v2.1.0. Enhanced security protocols now active across all AeThex infrastructure.</div>
</div>
<div class="message">
<div class="message-avatar" style="background: linear-gradient(135deg, #ff0000, #cc0000);">T</div>
<div class="message-content">
<div class="message-header">
<span class="message-author">Trevor</span>
<span class="message-badge foundation">Foundation</span>
<span class="message-time">10:34 AM</span>
</div>
<div class="message-text">
Just pushed the authentication updates. All services should automatically migrate to the new protocols within 24 hours.
</div>
</div>
</div>
<div class="message">
<div class="message-avatar" style="background: linear-gradient(135deg, #0066ff, #003380);">M</div>
<div class="message-content">
<div class="message-header">
<span class="message-author">Marcus</span>
<span class="message-time">10:41 AM</span>
</div>
<div class="message-text">
Excellent work! I've been testing the new Passport integration and it's incredibly smooth. The Trinity color-coding in the UI makes it really clear which division is handling what.
</div>
</div>
</div>
<div class="message-system labs">
<div class="system-label labs">[LABS] Experimental Feature Alert</div>
<div>Nexus Engine v2.0-beta now available for testing. New cross-platform sync reduces latency by 40%. Join #labs-testing to participate.</div>
</div>
<div class="message">
<div class="message-avatar" style="background: linear-gradient(135deg, #ffa500, #ff8c00);">S</div>
<div class="message-content">
<div class="message-header">
<span class="message-author">Sarah</span>
<span class="message-badge labs">Labs</span>
<span class="message-time">11:15 AM</span>
</div>
<div class="message-text">
The Nexus v2 parallel compilation is insane. Cut my build time from 3 minutes to under 2. Still some edge cases with complex state synchronization but wow... ⚠️
</div>
</div>
</div>
<div class="message">
<div class="message-avatar" style="background: linear-gradient(135deg, #ff0000, #0066ff, #ffa500);">A</div>
<div class="message-content">
<div class="message-header">
<span class="message-author">Anderson</span>
<span class="message-badge foundation">Founder</span>
<span class="message-time">11:47 AM</span>
</div>
<div class="message-text">
Love seeing the Trinity infrastructure working in harmony. Foundation keeping everything secure, Labs pushing the boundaries, Corporation delivering production-ready tools. This is exactly the vision.
</div>
</div>
</div>
<div class="message">
<div class="message-avatar" style="background: #1a1a1a;">D</div>
<div class="message-content">
<div class="message-header">
<span class="message-author">DevUser_2847</span>
<span class="message-time">12:03 PM</span>
</div>
<div class="message-text">
Quick question - when using AeThex Studio, does the Terminal automatically connect to all three Trinity divisions, or do I need to configure that?
</div>
</div>
</div>
<div class="message-system corporation">
<div class="system-label corporation">[CORPORATION] Service Update</div>
<div>AeThex Studio Pro users: New Railway deployment templates available. Optimized configurations for Foundation APIs, Corporation services, and Labs experiments.</div>
</div>
</div>
<div class="message-input-container">
<input
type="text"
class="message-input"
placeholder="Message #general (Foundation infrastructure channel)"
/>
</div>
</div>
<!-- Member Sidebar -->
<div class="member-sidebar">
<div class="member-header">Members — 128</div>
<div class="member-list">
<div class="member-section">
<div class="member-section-title">Foundation Team — 8</div>
<div class="member-item">
<div class="member-avatar-small" style="background: linear-gradient(135deg, #ff0000, #cc0000);">
A
<div class="online-indicator online"></div>
</div>
<div class="member-name">Anderson</div>
</div>
<div class="member-item">
<div class="member-avatar-small" style="background: linear-gradient(135deg, #ff0000, #cc0000);">
T
<div class="online-indicator online"></div>
</div>
<div class="member-name">Trevor</div>
</div>
</div>
<div class="member-section">
<div class="member-section-title">Labs Team — 12</div>
<div class="member-item">
<div class="member-avatar-small" style="background: linear-gradient(135deg, #ffa500, #ff8c00);">
S
<div class="online-indicator labs"></div>
</div>
<div class="member-name">Sarah</div>
<div class="member-activity">Testing v2.0</div>
</div>
</div>
<div class="member-section">
<div class="member-section-title">Developers — 47</div>
<div class="member-item">
<div class="member-avatar-small">
M
<div class="online-indicator in-game"></div>
</div>
<div class="member-name">Marcus</div>
<div class="member-activity">Building</div>
</div>
<div class="member-item">
<div class="member-avatar-small">
D
<div class="online-indicator online"></div>
</div>
<div class="member-name">DevUser_2847</div>
</div>
</div>
<div class="member-section">
<div class="member-section-title">Community — 61</div>
<div class="member-item">
<div class="member-avatar-small">J</div>
<div class="member-name">JohnDev</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View file

@ -1,7 +1,8 @@
import { defineConfig } from 'astro/config';
import tailwind from '@astrojs/tailwind';
import react from '@astrojs/react';
export default defineConfig({
integrations: [tailwind()],
integrations: [tailwind(), react()],
site: 'https://aethex-connect.com',
});

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,14 @@
"preview": "astro preview"
},
"dependencies": {
"astro": "^4.0.0"
"@astrojs/react": "^4.4.2",
"@types/react": "^19.2.8",
"@types/react-dom": "^19.2.3",
"astro": "^4.0.0",
"mumble-client": "^1.3.0",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"simple-peer": "^9.11.1"
},
"devDependencies": {
"@astrojs/tailwind": "^5.0.0",

View file

@ -0,0 +1,87 @@
import React, { useState } from "react";
/**
* UI for linking AeThex and Matrix accounts.
* 1. User logs in with AeThex credentials (simulate for now)
* 2. User logs in with Matrix credentials
* 3. Store mapping in localStorage (or call backend in real app)
*/
export default function AccountLinker({ onLinked }) {
const [aethexUser, setAethexUser] = useState("");
const [aethexPass, setAethexPass] = useState("");
const [matrixUser, setMatrixUser] = useState("");
const [matrixPass, setMatrixPass] = useState("");
const [step, setStep] = useState(1);
const [error, setError] = useState(null);
// Simulate AeThex auth (replace with real API call)
const handleAethexLogin = (e) => {
e.preventDefault();
if (aethexUser && aethexPass) {
setStep(2);
setError(null);
} else {
setError("Enter AeThex username and password.");
}
};
// Simulate Matrix auth (let MatrixProvider handle real login)
const handleMatrixLogin = (e) => {
e.preventDefault();
if (matrixUser && matrixPass) {
// Store mapping (simulate)
localStorage.setItem("aethex-matrix-link", JSON.stringify({ aethexUser, matrixUser }));
setError(null);
if (onLinked) onLinked({ aethexUser, matrixUser, matrixPass });
} else {
setError("Enter Matrix username and password.");
}
};
return (
<div className="account-linker bg-[#181818] p-6 rounded-lg max-w-md mx-auto mt-10 shadow-lg">
<h2 className="text-xl font-bold mb-4 text-white">Link AeThex & Matrix Accounts</h2>
{error && <div className="mb-2 text-red-400">{error}</div>}
{step === 1 && (
<form onSubmit={handleAethexLogin} className="flex flex-col gap-3">
<input
type="text"
placeholder="AeThex Username"
className="px-3 py-2 rounded bg-[#222] text-white border border-[#333]"
value={aethexUser}
onChange={e => setAethexUser(e.target.value)}
autoFocus
/>
<input
type="password"
placeholder="AeThex Password"
className="px-3 py-2 rounded bg-[#222] text-white border border-[#333]"
value={aethexPass}
onChange={e => setAethexPass(e.target.value)}
/>
<button type="submit" className="bg-blue-600 text-white rounded py-2 font-semibold mt-2">Continue to Matrix</button>
</form>
)}
{step === 2 && (
<form onSubmit={handleMatrixLogin} className="flex flex-col gap-3">
<input
type="text"
placeholder="Matrix Username (@user:matrix.org)"
className="px-3 py-2 rounded bg-[#222] text-white border border-[#333]"
value={matrixUser}
onChange={e => setMatrixUser(e.target.value)}
autoFocus
/>
<input
type="password"
placeholder="Matrix Password"
className="px-3 py-2 rounded bg-[#222] text-white border border-[#333]"
value={matrixPass}
onChange={e => setMatrixPass(e.target.value)}
/>
<button type="submit" className="bg-green-600 text-white rounded py-2 font-semibold mt-2">Link Accounts & Login</button>
</form>
)}
</div>
);
}

View file

@ -0,0 +1,68 @@
import React, { createContext, useContext, useEffect, useState, useCallback } from "react";
import sdk from "matrix-js-sdk";
const MatrixContext = createContext(null);
export function useMatrix() {
return useContext(MatrixContext);
}
export function MatrixProvider({ children }) {
const [client, setClient] = useState(null);
const [rooms, setRooms] = useState([]);
const [messages, setMessages] = useState([]);
const [user, setUser] = useState(null);
const [currentRoomId, setCurrentRoomId] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// Login to Matrix
const login = useCallback(async (username, password, homeserver = "https://matrix.org") => {
setLoading(true);
setError(null);
try {
const matrixClient = sdk.createClient({ baseUrl: homeserver });
const resp = await matrixClient.loginWithPassword(username, password);
matrixClient.startClient({ initialSyncLimit: 10 });
setClient(matrixClient);
setUser({ userId: resp.user_id, accessToken: resp.access_token });
setLoading(false);
// Listen for sync and events
matrixClient.on("sync", (state) => {
if (state === "PREPARED") {
setRooms(matrixClient.getRooms());
}
});
matrixClient.on("Room.timeline", (event, room) => {
if (room.roomId === currentRoomId && event.getType() === "m.room.message") {
setMessages((msgs) => [...msgs, event.event]);
}
});
} catch (e) {
setError(e.message);
setLoading(false);
}
}, [currentRoomId]);
// Join a room and fetch messages
const joinRoom = useCallback(async (roomId) => {
if (!client) return;
setCurrentRoomId(roomId);
const room = client.getRoom(roomId);
if (room) {
setMessages(room.timeline.filter(e => e.getType() === "m.room.message").map(e => e.event));
}
}, [client]);
// Send a message
const sendMessage = useCallback(async (roomId, text) => {
if (!client) return;
await client.sendTextMessage(roomId, text);
}, [client]);
return (
<MatrixContext.Provider value={{ client, rooms, messages, user, login, joinRoom, sendMessage, currentRoomId, loading, error }}>
{children}
</MatrixContext.Provider>
);
}

View file

@ -0,0 +1,68 @@
import React from "react";
import { useWebRTC } from "../webrtc/WebRTCProvider.jsx";
export default function ChannelSidebar() {
const { joined, joinVoice, leaveVoice } = useWebRTC();
return (
<div className="channel-sidebar w-72 bg-[#0f0f0f] border-r border-[#1a1a1a] flex flex-col">
{/* Server Header */}
<div className="server-header p-4 border-b border-[#1a1a1a] font-bold text-base flex items-center justify-between">
<span>AeThex Foundation</span>
<span className="server-badge foundation text-xs px-2 py-1 rounded bg-red-900/20 text-red-500 border border-red-500 uppercase tracking-wider">Official</span>
</div>
{/* Channel List */}
<div className="channel-list flex-1 overflow-y-auto py-2">
<div className="channel-category px-4 pt-4 pb-2 text-xs uppercase tracking-widest text-gray-500 font-bold">Announcements</div>
<div className="channel-item flex items-center gap-2 px-4 py-2 mx-2 rounded cursor-pointer text-sm hover:bg-[#1a1a1a]">
<span className="channel-icon">📢</span>
<span className="channel-name flex-1">updates</span>
<span className="channel-badge text-xs bg-red-600 text-white px-2 rounded-full">3</span>
</div>
<div className="channel-item flex items-center gap-2 px-4 py-2 mx-2 rounded cursor-pointer text-sm hover:bg-[#1a1a1a]">
<span className="channel-icon">📜</span>
<span className="channel-name flex-1">changelog</span>
</div>
<div className="channel-category px-4 pt-4 pb-2 text-xs uppercase tracking-widest text-gray-500 font-bold">Development</div>
<div className="channel-item active flex items-center gap-2 px-4 py-2 mx-2 rounded cursor-pointer text-sm bg-[#1a1a1a]">
<span className="channel-icon">#</span>
<span className="channel-name flex-1">general</span>
</div>
<div className="channel-item flex items-center gap-2 px-4 py-2 mx-2 rounded cursor-pointer text-sm hover:bg-[#1a1a1a]">
<span className="channel-icon">#</span>
<span className="channel-name flex-1">api-discussion</span>
</div>
<div className="channel-item flex items-center gap-2 px-4 py-2 mx-2 rounded cursor-pointer text-sm hover:bg-[#1a1a1a]">
<span className="channel-icon">#</span>
<span className="channel-name flex-1">passport-development</span>
</div>
<div className="channel-category px-4 pt-4 pb-2 text-xs uppercase tracking-widest text-gray-500 font-bold">Support</div>
<div className="channel-item flex items-center gap-2 px-4 py-2 mx-2 rounded cursor-pointer text-sm hover:bg-[#1a1a1a]">
<span className="channel-icon"></span>
<span className="channel-name flex-1">help</span>
</div>
<div className="channel-item flex items-center gap-2 px-4 py-2 mx-2 rounded cursor-pointer text-sm hover:bg-[#1a1a1a]">
<span className="channel-icon">🐛</span>
<span className="channel-name flex-1">bug-reports</span>
</div>
<div className="channel-category px-4 pt-4 pb-2 text-xs uppercase tracking-widest text-gray-500 font-bold">Voice Channels</div>
<div className={`channel-item flex items-center gap-2 px-4 py-2 mx-2 rounded cursor-pointer text-sm hover:bg-[#1a1a1a] ${joined ? "bg-blue-900/30" : ""}`}
onClick={joined ? leaveVoice : joinVoice}>
<span className="channel-icon">🔊</span>
<span className="channel-name flex-1">Nexus Lounge</span>
<span className="text-gray-500 text-xs">{joined ? "Connected" : "3"}</span>
</div>
</div>
{/* User Presence */}
<div className="user-presence p-3 border-t border-[#1a1a1a] flex items-center gap-3 text-sm">
<div className="user-avatar w-10 h-10 rounded-full flex items-center justify-center font-bold bg-gradient-to-tr from-red-600 via-blue-600 to-orange-400">A</div>
<div className="user-info flex-1">
<div className="user-name font-bold mb-0.5">Anderson</div>
<div className="user-status flex items-center gap-1 text-xs text-gray-500">
<span className="status-dot w-2 h-2 rounded-full bg-green-400 shadow-green-400/50 shadow" />
<span>Building AeThex</span>
</div>
</div>
</div>
</div>
);
}

View file

@ -0,0 +1,64 @@
import React, { useEffect } from "react";
import Message from "./Message";
import MessageInput from "./MessageInput";
import { useMatrix } from "../matrix/MatrixProvider.jsx";
// Default room to join (replace with your Matrix room ID)
const DEFAULT_ROOM_ID = "!foundation:matrix.org";
export default function ChatArea() {
const { messages, joinRoom, currentRoomId, user } = useMatrix();
// Join the default room on login
useEffect(() => {
if (user && !currentRoomId) {
joinRoom(DEFAULT_ROOM_ID);
}
}, [user, currentRoomId, joinRoom]);
return (
<div className="chat-area flex flex-col flex-1 bg-[#0a0a0a]">
{/* Chat Header */}
<div className="chat-header px-5 py-4 border-b border-[#1a1a1a] flex items-center gap-3">
<span className="channel-name-header flex-1 font-bold text-base"># general</span>
<div className="chat-tools flex gap-4 text-sm text-gray-500">
<span className="chat-tool cursor-pointer hover:text-blue-500">🔔</span>
<span className="chat-tool cursor-pointer hover:text-blue-500">📌</span>
<span className="chat-tool cursor-pointer hover:text-blue-500">👥 128</span>
<span className="chat-tool cursor-pointer hover:text-blue-500">🔍</span>
</div>
</div>
{/* Messages */}
<div className="chat-messages flex-1 overflow-y-auto px-5 py-5">
{messages && messages.length > 0 ? (
messages.map((msg, i) => (
<Message key={i} {...matrixToMessageProps(msg)} />
))
) : (
<div className="text-gray-500 text-center">No messages yet.</div>
)}
</div>
{/* Message Input */}
<div className="message-input-container px-5 py-5 border-t border-[#1a1a1a]">
<MessageInput />
</div>
</div>
);
}
// Helper to convert Matrix event to Message props
function matrixToMessageProps(event) {
if (!event) return {};
if (event.type === "m.room.message") {
return {
type: "user",
author: event.sender?.split(":")[0]?.replace("@", "") || "User",
text: event.content?.body || "",
time: new Date(event.origin_server_ts).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }),
avatar: event.sender?.charAt(1)?.toUpperCase() || "U",
avatarBg: "from-blue-600 to-blue-900",
};
}
// Add system message mapping if needed
return { type: "system", label: "MATRIX", text: JSON.stringify(event), className: "foundation" };
}

View file

@ -0,0 +1,14 @@
import { WebRTCProvider } from "../webrtc/WebRTCProvider.jsx";
export default function MainLayout() {
return (
<WebRTCProvider>
<div className="connect-container flex h-screen">
<ServerList />
<ChannelSidebar />
<ChatArea />
<MemberSidebar />
</div>
</WebRTCProvider>
);
}

View file

@ -0,0 +1,98 @@
import React, { useEffect, useRef } from "react";
import { useWebRTC } from "../webrtc/WebRTCProvider.jsx";
const members = [
{ section: "Foundation Team — 8", users: [
{ name: "Anderson", avatar: "A", status: "online", avatarBg: "from-red-600 to-red-800" },
{ name: "Trevor", avatar: "T", status: "online", avatarBg: "from-red-600 to-red-800" },
]},
{ section: "Labs Team — 12", users: [
{ name: "Sarah", avatar: "S", status: "labs", avatarBg: "from-orange-400 to-orange-700", activity: "Testing v2.0" },
]},
{ section: "Developers — 47", users: [
{ name: "Marcus", avatar: "M", status: "in-game", avatarBg: "bg-[#1a1a1a]", activity: "Building" },
{ name: "DevUser_2847", avatar: "D", status: "online", avatarBg: "bg-[#1a1a1a]" },
]},
{ section: "Community — 61", users: [
{ name: "JohnDev", avatar: "J", status: "offline", avatarBg: "bg-[#1a1a1a]" },
]},
];
export default function MemberSidebar() {
const { joined, peers, localStream } = useWebRTC();
// Helper to render audio for remote streams
function RemoteAudio({ stream }) {
const audioRef = useRef();
useEffect(() => {
if (audioRef.current && stream) {
audioRef.current.srcObject = stream;
}
}, [stream]);
return <audio ref={audioRef} autoPlay playsInline />;
}
// Helper to render local audio (muted)
function LocalAudio() {
const audioRef = useRef();
useEffect(() => {
if (audioRef.current && localStream) {
audioRef.current.srcObject = localStream;
}
}, [localStream]);
return <audio ref={audioRef} autoPlay playsInline muted />;
}
return (
<div className="member-sidebar w-72 bg-[#0f0f0f] border-l border-[#1a1a1a] flex flex-col">
<div className="member-header p-4 border-b border-[#1a1a1a] text-xs uppercase tracking-widest text-gray-500">Members 128</div>
<div className="member-list flex-1 overflow-y-auto py-3">
{/* Show all connected voice users */}
{(joined || (peers && peers.length > 0)) && (
<div className="member-section mb-4">
<div className="member-section-title px-4 py-2 text-xs uppercase tracking-wider text-blue-400 font-bold">Voice Channel Nexus Lounge</div>
{/* Local user */}
{joined && (
<div className="member-item flex items-center gap-3 px-4 py-1.5 cursor-pointer bg-blue-900/20">
<div className="member-avatar-small w-8 h-8 rounded-full flex items-center justify-center font-bold text-sm relative bg-gradient-to-tr from-blue-600 to-blue-900">
<span role="img" aria-label="mic">🎤</span>
<div className="online-indicator absolute -bottom-0.5 -right-0.5 w-3 h-3 rounded-full border-2 border-[#0f0f0f] bg-blue-400"></div>
</div>
<div className="member-name flex-1 text-sm">You (Voice Connected)</div>
<div className="member-activity text-xs text-blue-400">Live</div>
<LocalAudio />
</div>
)}
{/* Remote peers */}
{peers && peers.map(({ peerId, stream }) => (
<div key={peerId} className="member-item flex items-center gap-3 px-4 py-1.5 cursor-pointer bg-blue-900/10">
<div className="member-avatar-small w-8 h-8 rounded-full flex items-center justify-center font-bold text-sm relative bg-gradient-to-tr from-blue-600 to-blue-900">
<span role="img" aria-label="mic">🎤</span>
<div className="online-indicator absolute -bottom-0.5 -right-0.5 w-3 h-3 rounded-full border-2 border-[#0f0f0f] bg-blue-400"></div>
</div>
<div className="member-name flex-1 text-sm">{peerId}</div>
<div className="member-activity text-xs text-blue-400">Live</div>
<RemoteAudio stream={stream} />
</div>
))}
</div>
)}
{members.map((section, i) => (
<div key={i} className="member-section mb-4">
<div className="member-section-title px-4 py-2 text-xs uppercase tracking-wider text-gray-500 font-bold">{section.section}</div>
{section.users.map((user, j) => (
<div key={j} className="member-item flex items-center gap-3 px-4 py-1.5 cursor-pointer hover:bg-[#1a1a1a]">
<div className={`member-avatar-small w-8 h-8 rounded-full flex items-center justify-center font-bold text-sm relative bg-gradient-to-tr ${user.avatarBg}`}>
{user.avatar}
<div className={`online-indicator absolute -bottom-0.5 -right-0.5 w-3 h-3 rounded-full border-2 border-[#0f0f0f] ${user.status === "online" ? "bg-green-400" : user.status === "in-game" ? "bg-blue-500" : user.status === "labs" ? "bg-orange-400" : "bg-gray-700"}`}></div>
</div>
<div className="member-name flex-1 text-sm">{user.name}</div>
{user.activity && <div className="member-activity text-xs text-gray-500">{user.activity}</div>}
</div>
))}
</div>
))}
</div>
</div>
);
}

View file

@ -0,0 +1,27 @@
import React from "react";
export default function Message(props) {
if (props.type === "system") {
return (
<div className={`message-system ${props.className} bg-[#0f0f0f] border-l-4 pl-4 pr-4 py-3 mb-4 text-sm`}>
<div className={`system-label ${props.className} text-xs uppercase tracking-wider font-bold mb-1`}>[{props.label}] System Announcement</div>
<div>{props.text}</div>
</div>
);
}
return (
<div className="message flex gap-4 mb-5 p-3 rounded transition hover:bg-[#0f0f0f]">
<div className={`message-avatar w-10 h-10 rounded-full flex items-center justify-center font-bold text-base flex-shrink-0 bg-gradient-to-tr ${props.avatarBg}`}>{props.avatar}</div>
<div className="message-content flex-1">
<div className="message-header flex items-baseline gap-3 mb-1">
<span className="message-author font-bold">{props.author}</span>
{props.badge && (
<span className={`message-badge ${props.className} text-xs px-2 py-1 rounded uppercase tracking-wider font-bold`}>{props.badge}</span>
)}
<span className="message-time text-xs text-gray-500">{props.time}</span>
</div>
<div className="message-text leading-relaxed text-gray-300">{props.text}</div>
</div>
</div>
);
}

View file

@ -0,0 +1,32 @@
import React, { useState } from "react";
import { useMatrix } from "../matrix/MatrixProvider.jsx";
const DEFAULT_ROOM_ID = "!foundation:matrix.org";
export default function MessageInput() {
const [text, setText] = useState("");
const { sendMessage, user, currentRoomId } = useMatrix();
const handleSend = async (e) => {
e.preventDefault();
if (!text.trim() || !user) return;
await sendMessage(currentRoomId || DEFAULT_ROOM_ID, text);
setText("");
};
return (
<form className="flex items-center gap-2" onSubmit={handleSend}>
<button type="button" className="attachButton w-10 h-10 flex items-center justify-center rounded bg-[#1a1a1a] text-xl text-gray-400 mr-2">+</button>
<input
type="text"
className="message-input flex-1 bg-[#0f0f0f] border border-[#1a1a1a] rounded-lg px-4 py-3 text-gray-200 text-sm focus:outline-none focus:border-blue-500 placeholder:text-gray-500"
placeholder="Message #general (Foundation infrastructure channel)"
maxLength={2000}
value={text}
onChange={e => setText(e.target.value)}
disabled={!user}
/>
<button type="submit" className="sendButton w-10 h-10 flex items-center justify-center rounded bg-blue-600 text-xl text-white ml-2">3a4</button>
</form>
);
}

View file

@ -0,0 +1,30 @@
import React 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" },
];
export default function ServerList() {
return (
<div className="server-list flex flex-col items-center py-3 gap-3 w-20 bg-[#0d0d0d] border-r border-[#1a1a1a]">
{servers.map((srv, i) =>
srv.id === "divider" ? (
<div key={i} className="server-divider w-10 h-0.5 bg-[#1a1a1a] my-1" />
) : (
<div
key={srv.id}
className={`server-icon ${srv.className} ${srv.active ? "active" : ""} w-14 h-14 rounded-xl flex items-center justify-center font-bold text-lg cursor-pointer transition-all relative`}
>
{srv.label}
</div>
)
)}
</div>
);
}

View file

@ -0,0 +1,57 @@
@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@300;400;500;700&display=swap');
html, body, #root {
height: 100%;
margin: 0;
padding: 0;
font-family: 'Roboto Mono', monospace;
background: #0a0a0a;
color: #e0e0e0;
}
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
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;
}
.connect-container {
height: 100vh;
display: flex;
}
.server-icon, .user-avatar, .member-avatar-small {
background: rgba(26,26,26,0.85);
backdrop-filter: blur(6px);
}
.channel-item.active, .channel-item:hover, .member-item:hover {
background: rgba(26,26,26,0.85);
backdrop-filter: blur(4px);
}
.message-input, .message-input-container {
background: rgba(15,15,15,0.95);
backdrop-filter: blur(4px);
}
::-webkit-scrollbar {
width: 8px;
background: #111;
}
::-webkit-scrollbar-thumb {
background: #222;
border-radius: 4px;
}

View file

@ -0,0 +1,103 @@
import React, { createContext, useContext, useRef, useState, useEffect, useCallback } from "react";
import Peer from "simple-peer";
import { useMatrix } from "../matrix/MatrixProvider.jsx";
const WebRTCContext = createContext(null);
export function useWebRTC() {
return useContext(WebRTCContext);
}
const [peers, setPeers] = useState([]); // [{ peerId, peer, stream }]
const [localStream, setLocalStream] = useState(null);
const [joined, setJoined] = useState(false);
const peersRef = useRef({});
const { client, user, currentRoomId } = useMatrix();
const SIGNAL_EVENT = "org.aethex.voice.signal";
const VOICE_ROOM = currentRoomId || "!foundation:matrix.org";
// Helper: Send signal via Matrix event
const sendSignal = useCallback((to, data) => {
if (!client || !VOICE_ROOM) return;
client.sendEvent(VOICE_ROOM, SIGNAL_EVENT, { to, from: user?.userId, data }, "");
}, [client, VOICE_ROOM, user]);
// Join a voice channel (start local audio, announce self)
const joinVoice = async () => {
if (localStream || !client || !user) return;
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
setLocalStream(stream);
setJoined(true);
// Announce self to others
client.sendEvent(VOICE_ROOM, SIGNAL_EVENT, { join: true, from: user.userId }, "");
} catch (err) {
alert("Could not access microphone: " + err.message);
}
};
// Leave voice channel (close peers, announce leave)
const leaveVoice = () => {
if (localStream) {
localStream.getTracks().forEach(track => track.stop());
setLocalStream(null);
}
Object.values(peersRef.current).forEach(({ peer }) => peer.destroy());
peersRef.current = {};
setPeers([]);
setJoined(false);
if (client && user) {
client.sendEvent(VOICE_ROOM, SIGNAL_EVENT, { leave: true, from: user.userId }, "");
}
};
// Handle incoming Matrix signal events
useEffect(() => {
if (!client || !user || !VOICE_ROOM) return;
const handler = (event) => {
if (event.getType() !== SIGNAL_EVENT) return;
const { from, to, data, join, leave } = event.getContent();
if (from === user.userId) return;
// Handle join: create peer if not exists
if (join) {
if (!peersRef.current[from]) {
const initiator = user.userId > from; // simple deterministic initiator
const peer = new Peer({ initiator, trickle: false, stream: localStream });
peer.on("signal", signal => sendSignal(from, signal));
peer.on("stream", remoteStream => {
setPeers(p => [...p, { peerId: from, peer, stream: remoteStream }]);
});
peer.on("close", () => {
setPeers(p => p.filter(x => x.peerId !== from));
delete peersRef.current[from];
});
peersRef.current[from] = { peer };
}
}
// Handle leave: remove peer
if (leave && peersRef.current[from]) {
peersRef.current[from].peer.destroy();
delete peersRef.current[from];
setPeers(p => p.filter(x => x.peerId !== from));
}
// Handle signal: pass to peer
if (data && peersRef.current[from]) {
peersRef.current[from].peer.signal(data);
}
};
client.on("event", handler);
return () => client.removeListener("event", handler);
}, [client, user, VOICE_ROOM, localStream, sendSignal]);
// Announce self to new joiners
useEffect(() => {
if (!joined || !client || !user) return;
client.sendEvent(VOICE_ROOM, SIGNAL_EVENT, { join: true, from: user.userId }, "");
}, [joined, client, user, VOICE_ROOM]);
return (
<WebRTCContext.Provider value={{ peers, localStream, joined, joinVoice, leaveVoice }}>
{children}
</WebRTCContext.Provider>
);
}

View file

@ -1,63 +1,19 @@
---
import Layout from '../layouts/Layout.astro';
import Hero from '../components/Hero.astro';
import Features from '../components/Features.astro';
import Pricing from '../components/Pricing.astro';
import MainLayout from '../components/mockup/MainLayout.jsx';
import AccountLinker from '../components/auth/AccountLinker.jsx';
import { useState } from 'react';
import { MatrixProvider } from '../components/matrix/MatrixProvider.jsx';
---
<Layout title="AeThex Connect - Gaming Communication Platform">
<main>
<Hero />
<Features />
<Pricing />
<!-- Download Section -->
<section id="download" class="download">
<div class="container">
<h2>Download <span class="gradient-text">AeThex Connect</span></h2>
<p>Available on all platforms</p>
<div class="download-buttons">
<a href="#" class="download-btn">
<span class="icon">🍎</span>
<div>
<div class="label">Download on the</div>
<div class="platform">App Store</div>
</div>
</a>
<a href="#" class="download-btn">
<span class="icon">🤖</span>
<div>
<div class="label">Get it on</div>
<div class="platform">Google Play</div>
</div>
</a>
<a href="#" class="download-btn">
<span class="icon">💻</span>
<div>
<div class="label">Desktop for</div>
<div class="platform">Windows/Mac/Linux</div>
</div>
</a>
</div>
</div>
</section>
<!-- Footer -->
<footer class="footer">
<div class="container">
<div class="footer-content">
<div class="footer-brand">
<h3>AeThex Connect</h3>
<p>Next-generation communication for gamers</p>
</div>
<div class="footer-links">
<div>
<h4>Product</h4>
<ul>
<li><a href="#features">Features</a></li>
<li><a href="#pricing">Pricing</a></li>
<li><a href="#download">Download</a></li>
<Layout title="AeThex Connect">
<AccountLinker client:load onLinked={({ matrixUser, matrixPass }) => {
// Optionally, auto-login to Matrix here
// This is a placeholder; actual login logic should be in a React component
}} />
<MatrixProvider>
<MainLayout client:load />
</MatrixProvider>
</ul>
</div>
<div>

View file

@ -0,0 +1,7 @@
import React from "react";
import MainLayout from "../components/mockup/MainLayout";
import "../components/mockup/global.css";
export default function MockupPage() {
return <MainLayout />;
}

7
astro-site/tsconfig.json Normal file
View file

@ -0,0 +1,7 @@
{
"extends": "astro/tsconfigs/base",
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "react"
}
}

View file

@ -0,0 +1,7 @@
import React from "react";
import MainLayout from "./components/MainLayout";
import "./global.css";
const App: React.FC = () => <MainLayout />;
export default App;

View file

@ -0,0 +1,65 @@
import React from "react";
const ChannelSidebar: React.FC = () => (
<div className="channel-sidebar w-72 bg-[#0f0f0f] border-r border-[#1a1a1a] flex flex-col">
{/* Server Header */}
<div className="server-header p-4 border-b border-[#1a1a1a] font-bold text-base flex items-center justify-between">
<span>AeThex Foundation</span>
<span className="server-badge foundation text-xs px-2 py-1 rounded bg-red-900/20 text-red-500 border border-red-500 uppercase tracking-wider">Official</span>
</div>
{/* Channel List */}
<div className="channel-list flex-1 overflow-y-auto py-2">
<div className="channel-category px-4 pt-4 pb-2 text-xs uppercase tracking-widest text-gray-500 font-bold">Announcements</div>
<div className="channel-item flex items-center gap-2 px-4 py-2 mx-2 rounded cursor-pointer text-sm hover:bg-[#1a1a1a]">
<span className="channel-icon">📢</span>
<span className="channel-name flex-1">updates</span>
<span className="channel-badge text-xs bg-red-600 text-white px-2 rounded-full">3</span>
</div>
<div className="channel-item flex items-center gap-2 px-4 py-2 mx-2 rounded cursor-pointer text-sm hover:bg-[#1a1a1a]">
<span className="channel-icon">📜</span>
<span className="channel-name flex-1">changelog</span>
</div>
<div className="channel-category px-4 pt-4 pb-2 text-xs uppercase tracking-widest text-gray-500 font-bold">Development</div>
<div className="channel-item active flex items-center gap-2 px-4 py-2 mx-2 rounded cursor-pointer text-sm bg-[#1a1a1a]">
<span className="channel-icon">#</span>
<span className="channel-name flex-1">general</span>
</div>
<div className="channel-item flex items-center gap-2 px-4 py-2 mx-2 rounded cursor-pointer text-sm hover:bg-[#1a1a1a]">
<span className="channel-icon">#</span>
<span className="channel-name flex-1">api-discussion</span>
</div>
<div className="channel-item flex items-center gap-2 px-4 py-2 mx-2 rounded cursor-pointer text-sm hover:bg-[#1a1a1a]">
<span className="channel-icon">#</span>
<span className="channel-name flex-1">passport-development</span>
</div>
<div className="channel-category px-4 pt-4 pb-2 text-xs uppercase tracking-widest text-gray-500 font-bold">Support</div>
<div className="channel-item flex items-center gap-2 px-4 py-2 mx-2 rounded cursor-pointer text-sm hover:bg-[#1a1a1a]">
<span className="channel-icon"></span>
<span className="channel-name flex-1">help</span>
</div>
<div className="channel-item flex items-center gap-2 px-4 py-2 mx-2 rounded cursor-pointer text-sm hover:bg-[#1a1a1a]">
<span className="channel-icon">🐛</span>
<span className="channel-name flex-1">bug-reports</span>
</div>
<div className="channel-category px-4 pt-4 pb-2 text-xs uppercase tracking-widest text-gray-500 font-bold">Voice Channels</div>
<div className="channel-item flex items-center gap-2 px-4 py-2 mx-2 rounded cursor-pointer text-sm hover:bg-[#1a1a1a]">
<span className="channel-icon">🔊</span>
<span className="channel-name flex-1">Nexus Lounge</span>
<span className="text-gray-500 text-xs">3</span>
</div>
</div>
{/* User Presence */}
<div className="user-presence p-3 border-t border-[#1a1a1a] flex items-center gap-3 text-sm">
<div className="user-avatar w-10 h-10 rounded-full flex items-center justify-center font-bold bg-gradient-to-tr from-red-600 via-blue-600 to-orange-400">A</div>
<div className="user-info flex-1">
<div className="user-name font-bold mb-0.5">Anderson</div>
<div className="user-status flex items-center gap-1 text-xs text-gray-500">
<span className="status-dot w-2 h-2 rounded-full bg-green-400 shadow-green-400/50 shadow" />
<span>Building AeThex</span>
</div>
</div>
</div>
</div>
);
export default ChannelSidebar;

View file

@ -0,0 +1,41 @@
import React from "react";
import Message from "./Message";
import MessageInput from "./MessageInput";
const messages = [
{ type: "system", label: "FOUNDATION", text: "Foundation authentication services upgraded to v2.1.0. Enhanced security protocols now active across all AeThex infrastructure.", className: "foundation" },
{ type: "user", author: "Trevor", badge: "Foundation", time: "10:34 AM", text: "Just pushed the authentication updates. All services should automatically migrate to the new protocols within 24 hours.", avatar: "T", avatarBg: "from-red-600 to-red-800" },
{ type: "user", author: "Marcus", time: "10:41 AM", text: "Excellent work! I've been testing the new Passport integration and it's incredibly smooth. The Trinity color-coding in the UI makes it really clear which division is handling what.", avatar: "M", avatarBg: "from-blue-600 to-blue-900" },
{ type: "system", label: "LABS", text: "Nexus Engine v2.0-beta now available for testing. New cross-platform sync reduces latency by 40%. Join #labs-testing to participate.", className: "labs" },
{ type: "user", author: "Sarah", badge: "Labs", time: "11:15 AM", text: "The Nexus v2 parallel compilation is insane. Cut my build time from 3 minutes to under 2. Still some edge cases with complex state synchronization but wow... ⚠️", avatar: "S", avatarBg: "from-orange-400 to-orange-700" },
{ type: "user", author: "Anderson", badge: "Founder", time: "11:47 AM", text: "Love seeing the Trinity infrastructure working in harmony. Foundation keeping everything secure, Labs pushing the boundaries, Corporation delivering production-ready tools. This is exactly the vision.", avatar: "A", avatarBg: "from-red-600 via-blue-600 to-orange-400" },
{ type: "user", author: "DevUser_2847", time: "12:03 PM", text: "Quick question - when using AeThex Studio, does the Terminal automatically connect to all three Trinity divisions, or do I need to configure that?", avatar: "D", avatarBg: "bg-[#1a1a1a]" },
{ type: "system", label: "CORPORATION", text: "AeThex Studio Pro users: New Railway deployment templates available. Optimized configurations for Foundation APIs, Corporation services, and Labs experiments.", className: "corporation" },
];
const ChatArea: React.FC = () => (
<div className="chat-area flex flex-col flex-1 bg-[#0a0a0a]">
{/* Chat Header */}
<div className="chat-header px-5 py-4 border-b border-[#1a1a1a] flex items-center gap-3">
<span className="channel-name-header flex-1 font-bold text-base"># general</span>
<div className="chat-tools flex gap-4 text-sm text-gray-500">
<span className="chat-tool cursor-pointer hover:text-blue-500">🔔</span>
<span className="chat-tool cursor-pointer hover:text-blue-500">📌</span>
<span className="chat-tool cursor-pointer hover:text-blue-500">👥 128</span>
<span className="chat-tool cursor-pointer hover:text-blue-500">🔍</span>
</div>
</div>
{/* Messages */}
<div className="chat-messages flex-1 overflow-y-auto px-5 py-5">
{messages.map((msg, i) => (
<Message key={i} {...msg} />
))}
</div>
{/* Message Input */}
<div className="message-input-container px-5 py-5 border-t border-[#1a1a1a]">
<MessageInput />
</div>
</div>
);
export default ChatArea;

View file

@ -0,0 +1,18 @@
import React from "react";
import ServerList from "./ServerList";
import ChannelSidebar from "./ChannelSidebar";
import ChatArea from "./ChatArea";
import MemberSidebar from "./MemberSidebar";
const MainLayout: React.FC = () => {
return (
<div className="connect-container flex h-screen">
<ServerList />
<ChannelSidebar />
<ChatArea />
<MemberSidebar />
</div>
);
};
export default MainLayout;

View file

@ -0,0 +1,43 @@
import React from "react";
const members = [
{ section: "Foundation Team — 8", users: [
{ name: "Anderson", avatar: "A", status: "online", avatarBg: "from-red-600 to-red-800" },
{ name: "Trevor", avatar: "T", status: "online", avatarBg: "from-red-600 to-red-800" },
]},
{ section: "Labs Team — 12", users: [
{ name: "Sarah", avatar: "S", status: "labs", avatarBg: "from-orange-400 to-orange-700", activity: "Testing v2.0" },
]},
{ section: "Developers — 47", users: [
{ name: "Marcus", avatar: "M", status: "in-game", avatarBg: "bg-[#1a1a1a]", activity: "Building" },
{ name: "DevUser_2847", avatar: "D", status: "online", avatarBg: "bg-[#1a1a1a]" },
]},
{ section: "Community — 61", users: [
{ name: "JohnDev", avatar: "J", status: "offline", avatarBg: "bg-[#1a1a1a]" },
]},
];
const MemberSidebar: React.FC = () => (
<div className="member-sidebar w-72 bg-[#0f0f0f] border-l border-[#1a1a1a] flex flex-col">
<div className="member-header p-4 border-b border-[#1a1a1a] text-xs uppercase tracking-widest text-gray-500">Members 128</div>
<div className="member-list flex-1 overflow-y-auto py-3">
{members.map((section, i) => (
<div key={i} className="member-section mb-4">
<div className="member-section-title px-4 py-2 text-xs uppercase tracking-wider text-gray-500 font-bold">{section.section}</div>
{section.users.map((user, j) => (
<div key={j} className="member-item flex items-center gap-3 px-4 py-1.5 cursor-pointer hover:bg-[#1a1a1a]">
<div className={`member-avatar-small w-8 h-8 rounded-full flex items-center justify-center font-bold text-sm relative bg-gradient-to-tr ${user.avatarBg}`}>
{user.avatar}
<div className={`online-indicator absolute -bottom-0.5 -right-0.5 w-3 h-3 rounded-full border-2 border-[#0f0f0f] ${user.status === "online" ? "bg-green-400" : user.status === "in-game" ? "bg-blue-500" : user.status === "labs" ? "bg-orange-400" : "bg-gray-700"}`}></div>
</div>
<div className="member-name flex-1 text-sm">{user.name}</div>
{user.activity && <div className="member-activity text-xs text-gray-500">{user.activity}</div>}
</div>
))}
</div>
))}
</div>
</div>
);
export default MemberSidebar;

View file

@ -0,0 +1,41 @@
import React from "react";
interface MessageProps {
type: "system" | "user";
label?: string;
className?: string;
author?: string;
badge?: string;
time?: string;
text: string;
avatar?: string;
avatarBg?: string;
}
const Message: React.FC<MessageProps> = (props) => {
if (props.type === "system") {
return (
<div className={`message-system ${props.className} bg-[#0f0f0f] border-l-4 pl-4 pr-4 py-3 mb-4 text-sm`}>
<div className={`system-label ${props.className} text-xs uppercase tracking-wider font-bold mb-1`}>[{props.label}] System Announcement</div>
<div>{props.text}</div>
</div>
);
}
return (
<div className="message flex gap-4 mb-5 p-3 rounded transition hover:bg-[#0f0f0f]">
<div className={`message-avatar w-10 h-10 rounded-full flex items-center justify-center font-bold text-base flex-shrink-0 bg-gradient-to-tr ${props.avatarBg}`}>{props.avatar}</div>
<div className="message-content flex-1">
<div className="message-header flex items-baseline gap-3 mb-1">
<span className="message-author font-bold">{props.author}</span>
{props.badge && (
<span className={`message-badge ${props.className} text-xs px-2 py-1 rounded uppercase tracking-wider font-bold`}>{props.badge}</span>
)}
<span className="message-time text-xs text-gray-500">{props.time}</span>
</div>
<div className="message-text leading-relaxed text-gray-300">{props.text}</div>
</div>
</div>
);
};
export default Message;

View file

@ -0,0 +1,16 @@
import React from "react";
const MessageInput: React.FC = () => (
<div className="flex items-center gap-2">
<button className="attachButton w-10 h-10 flex items-center justify-center rounded bg-[#1a1a1a] text-xl text-gray-400 mr-2">+</button>
<input
type="text"
className="message-input flex-1 bg-[#0f0f0f] border border-[#1a1a1a] rounded-lg px-4 py-3 text-gray-200 text-sm focus:outline-none focus:border-blue-500 placeholder:text-gray-500"
placeholder="Message #general (Foundation infrastructure channel)"
maxLength={2000}
/>
<button className="sendButton w-10 h-10 flex items-center justify-center rounded bg-blue-600 text-xl text-white ml-2">🎤</button>
</div>
);
export default MessageInput;

View file

@ -0,0 +1,30 @@
import React 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" },
];
const ServerList: React.FC = () => (
<div className="server-list flex flex-col items-center py-3 gap-3 w-20 bg-[#0d0d0d] border-r border-[#1a1a1a]">
{servers.map((srv, i) =>
srv.id === "divider" ? (
<div key={i} className="server-divider w-10 h-0.5 bg-[#1a1a1a] my-1" />
) : (
<div
key={srv.id}
className={`server-icon ${srv.className} ${srv.active ? "active" : ""} w-14 h-14 rounded-xl flex items-center justify-center font-bold text-lg cursor-pointer transition-all relative`}
>
{srv.label}
</div>
)
)}
</div>
);
export default ServerList;

View file

@ -0,0 +1,61 @@
@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@300;400;500;700&display=swap');
html, body, #root {
height: 100%;
margin: 0;
padding: 0;
font-family: 'Roboto Mono', monospace;
background: #0a0a0a;
color: #e0e0e0;
}
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
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;
}
.connect-container {
height: 100vh;
display: flex;
}
/* Glassmorphism and accent classes for key elements */
.server-icon, .user-avatar, .member-avatar-small {
background: rgba(26,26,26,0.85);
backdrop-filter: blur(6px);
}
.channel-item.active, .channel-item:hover, .member-item:hover {
background: rgba(26,26,26,0.85);
backdrop-filter: blur(4px);
}
.message-input, .message-input-container {
background: rgba(15,15,15,0.95);
backdrop-filter: blur(4px);
}
/* Hide scrollbars for a cleaner look */
::-webkit-scrollbar {
width: 8px;
background: #111;
}
::-webkit-scrollbar-thumb {
background: #222;
border-radius: 4px;
}
/* Utility classes for gradients, badges, etc. can be extended as needed */

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AeThex Connect</title>
<link rel="stylesheet" href="./global.css" />
</head>
<body>
<div id="root"></div>
<script type="module" src="./index.tsx"></script>
</body>
</html>

View file

@ -0,0 +1,12 @@
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
const root = document.getElementById("root");
if (root) {
createRoot(root).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
}

View file

@ -0,0 +1,65 @@
import React from "react";
export default function ChannelSidebar() {
return (
<div className="channel-sidebar w-72 bg-[#0f0f0f] border-r border-[#1a1a1a] flex flex-col">
{/* Server Header */}
<div className="server-header p-4 border-b border-[#1a1a1a] font-bold text-base flex items-center justify-between">
<span>AeThex Foundation</span>
<span className="server-badge foundation text-xs px-2 py-1 rounded bg-red-900/20 text-red-500 border border-red-500 uppercase tracking-wider">Official</span>
</div>
{/* Channel List */}
<div className="channel-list flex-1 overflow-y-auto py-2">
<div className="channel-category px-4 pt-4 pb-2 text-xs uppercase tracking-widest text-gray-500 font-bold">Announcements</div>
<div className="channel-item flex items-center gap-2 px-4 py-2 mx-2 rounded cursor-pointer text-sm hover:bg-[#1a1a1a]">
<span className="channel-icon">📢</span>
<span className="channel-name flex-1">updates</span>
<span className="channel-badge text-xs bg-red-600 text-white px-2 rounded-full">3</span>
</div>
<div className="channel-item flex items-center gap-2 px-4 py-2 mx-2 rounded cursor-pointer text-sm hover:bg-[#1a1a1a]">
<span className="channel-icon">📜</span>
<span className="channel-name flex-1">changelog</span>
</div>
<div className="channel-category px-4 pt-4 pb-2 text-xs uppercase tracking-widest text-gray-500 font-bold">Development</div>
<div className="channel-item active flex items-center gap-2 px-4 py-2 mx-2 rounded cursor-pointer text-sm bg-[#1a1a1a]">
<span className="channel-icon">#</span>
<span className="channel-name flex-1">general</span>
</div>
<div className="channel-item flex items-center gap-2 px-4 py-2 mx-2 rounded cursor-pointer text-sm hover:bg-[#1a1a1a]">
<span className="channel-icon">#</span>
<span className="channel-name flex-1">api-discussion</span>
</div>
<div className="channel-item flex items-center gap-2 px-4 py-2 mx-2 rounded cursor-pointer text-sm hover:bg-[#1a1a1a]">
<span className="channel-icon">#</span>
<span className="channel-name flex-1">passport-development</span>
</div>
<div className="channel-category px-4 pt-4 pb-2 text-xs uppercase tracking-widest text-gray-500 font-bold">Support</div>
<div className="channel-item flex items-center gap-2 px-4 py-2 mx-2 rounded cursor-pointer text-sm hover:bg-[#1a1a1a]">
<span className="channel-icon"></span>
<span className="channel-name flex-1">help</span>
</div>
<div className="channel-item flex items-center gap-2 px-4 py-2 mx-2 rounded cursor-pointer text-sm hover:bg-[#1a1a1a]">
<span className="channel-icon">🐛</span>
<span className="channel-name flex-1">bug-reports</span>
</div>
<div className="channel-category px-4 pt-4 pb-2 text-xs uppercase tracking-widest text-gray-500 font-bold">Voice Channels</div>
<div className="channel-item flex items-center gap-2 px-4 py-2 mx-2 rounded cursor-pointer text-sm hover:bg-[#1a1a1a]">
<span className="channel-icon">🔊</span>
<span className="channel-name flex-1">Nexus Lounge</span>
<span className="text-gray-500 text-xs">3</span>
</div>
</div>
{/* User Presence */}
<div className="user-presence p-3 border-t border-[#1a1a1a] flex items-center gap-3 text-sm">
<div className="user-avatar w-10 h-10 rounded-full flex items-center justify-center font-bold bg-gradient-to-tr from-red-600 via-blue-600 to-orange-400">A</div>
<div className="user-info flex-1">
<div className="user-name font-bold mb-0.5">Anderson</div>
<div className="user-status flex items-center gap-1 text-xs text-gray-500">
<span className="status-dot w-2 h-2 rounded-full bg-green-400 shadow-green-400/50 shadow" />
<span>Building AeThex</span>
</div>
</div>
</div>
</div>
);
}

View file

@ -0,0 +1,41 @@
import React from "react";
import Message from "./Message";
import MessageInput from "./MessageInput";
const messages = [
{ type: "system", label: "FOUNDATION", text: "Foundation authentication services upgraded to v2.1.0. Enhanced security protocols now active across all AeThex infrastructure.", className: "foundation" },
{ type: "user", author: "Trevor", badge: "Foundation", time: "10:34 AM", text: "Just pushed the authentication updates. All services should automatically migrate to the new protocols within 24 hours.", avatar: "T", avatarBg: "from-red-600 to-red-800" },
{ type: "user", author: "Marcus", time: "10:41 AM", text: "Excellent work! I've been testing the new Passport integration and it's incredibly smooth. The Trinity color-coding in the UI makes it really clear which division is handling what.", avatar: "M", avatarBg: "from-blue-600 to-blue-900" },
{ type: "system", label: "LABS", text: "Nexus Engine v2.0-beta now available for testing. New cross-platform sync reduces latency by 40%. Join #labs-testing to participate.", className: "labs" },
{ type: "user", author: "Sarah", badge: "Labs", time: "11:15 AM", text: "The Nexus v2 parallel compilation is insane. Cut my build time from 3 minutes to under 2. Still some edge cases with complex state synchronization but wow... ⚠️", avatar: "S", avatarBg: "from-orange-400 to-orange-700" },
{ type: "user", author: "Anderson", badge: "Founder", time: "11:47 AM", text: "Love seeing the Trinity infrastructure working in harmony. Foundation keeping everything secure, Labs pushing the boundaries, Corporation delivering production-ready tools. This is exactly the vision.", avatar: "A", avatarBg: "from-red-600 via-blue-600 to-orange-400" },
{ type: "user", author: "DevUser_2847", time: "12:03 PM", text: "Quick question - when using AeThex Studio, does the Terminal automatically connect to all three Trinity divisions, or do I need to configure that?", avatar: "D", avatarBg: "bg-[#1a1a1a]" },
{ type: "system", label: "CORPORATION", text: "AeThex Studio Pro users: New Railway deployment templates available. Optimized configurations for Foundation APIs, Corporation services, and Labs experiments.", className: "corporation" },
];
export default function ChatArea() {
return (
<div className="chat-area flex flex-col flex-1 bg-[#0a0a0a]">
{/* Chat Header */}
<div className="chat-header px-5 py-4 border-b border-[#1a1a1a] flex items-center gap-3">
<span className="channel-name-header flex-1 font-bold text-base"># general</span>
<div className="chat-tools flex gap-4 text-sm text-gray-500">
<span className="chat-tool cursor-pointer hover:text-blue-500">🔔</span>
<span className="chat-tool cursor-pointer hover:text-blue-500">📌</span>
<span className="chat-tool cursor-pointer hover:text-blue-500">👥 128</span>
<span className="chat-tool cursor-pointer hover:text-blue-500">🔍</span>
</div>
</div>
{/* Messages */}
<div className="chat-messages flex-1 overflow-y-auto px-5 py-5">
{messages.map((msg, i) => (
<Message key={i} {...msg} />
))}
</div>
{/* Message Input */}
<div className="message-input-container px-5 py-5 border-t border-[#1a1a1a]">
<MessageInput />
</div>
</div>
);
}

View file

@ -0,0 +1,17 @@
import React from "react";
import ServerList from "./ServerList";
import ChannelSidebar from "./ChannelSidebar";
import ChatArea from "./ChatArea";
import MemberSidebar from "./MemberSidebar";
import "./global.css";
export default function MainLayout() {
return (
<div className="connect-container flex h-screen">
<ServerList />
<ChannelSidebar />
<ChatArea />
<MemberSidebar />
</div>
);
}

View file

@ -0,0 +1,43 @@
import React from "react";
const members = [
{ section: "Foundation Team — 8", users: [
{ name: "Anderson", avatar: "A", status: "online", avatarBg: "from-red-600 to-red-800" },
{ name: "Trevor", avatar: "T", status: "online", avatarBg: "from-red-600 to-red-800" },
]},
{ section: "Labs Team — 12", users: [
{ name: "Sarah", avatar: "S", status: "labs", avatarBg: "from-orange-400 to-orange-700", activity: "Testing v2.0" },
]},
{ section: "Developers — 47", users: [
{ name: "Marcus", avatar: "M", status: "in-game", avatarBg: "bg-[#1a1a1a]", activity: "Building" },
{ name: "DevUser_2847", avatar: "D", status: "online", avatarBg: "bg-[#1a1a1a]" },
]},
{ section: "Community — 61", users: [
{ name: "JohnDev", avatar: "J", status: "offline", avatarBg: "bg-[#1a1a1a]" },
]},
];
export default function MemberSidebar() {
return (
<div className="member-sidebar w-72 bg-[#0f0f0f] border-l border-[#1a1a1a] flex flex-col">
<div className="member-header p-4 border-b border-[#1a1a1a] text-xs uppercase tracking-widest text-gray-500">Members 128</div>
<div className="member-list flex-1 overflow-y-auto py-3">
{members.map((section, i) => (
<div key={i} className="member-section mb-4">
<div className="member-section-title px-4 py-2 text-xs uppercase tracking-wider text-gray-500 font-bold">{section.section}</div>
{section.users.map((user, j) => (
<div key={j} className="member-item flex items-center gap-3 px-4 py-1.5 cursor-pointer hover:bg-[#1a1a1a]">
<div className={`member-avatar-small w-8 h-8 rounded-full flex items-center justify-center font-bold text-sm relative bg-gradient-to-tr ${user.avatarBg}`}>
{user.avatar}
<div className={`online-indicator absolute -bottom-0.5 -right-0.5 w-3 h-3 rounded-full border-2 border-[#0f0f0f] ${user.status === "online" ? "bg-green-400" : user.status === "in-game" ? "bg-blue-500" : user.status === "labs" ? "bg-orange-400" : "bg-gray-700"}`}></div>
</div>
<div className="member-name flex-1 text-sm">{user.name}</div>
{user.activity && <div className="member-activity text-xs text-gray-500">{user.activity}</div>}
</div>
))}
</div>
))}
</div>
</div>
);
}

View file

@ -0,0 +1,27 @@
import React from "react";
export default function Message(props) {
if (props.type === "system") {
return (
<div className={`message-system ${props.className} bg-[#0f0f0f] border-l-4 pl-4 pr-4 py-3 mb-4 text-sm`}>
<div className={`system-label ${props.className} text-xs uppercase tracking-wider font-bold mb-1`}>[{props.label}] System Announcement</div>
<div>{props.text}</div>
</div>
);
}
return (
<div className="message flex gap-4 mb-5 p-3 rounded transition hover:bg-[#0f0f0f]">
<div className={`message-avatar w-10 h-10 rounded-full flex items-center justify-center font-bold text-base flex-shrink-0 bg-gradient-to-tr ${props.avatarBg}`}>{props.avatar}</div>
<div className="message-content flex-1">
<div className="message-header flex items-baseline gap-3 mb-1">
<span className="message-author font-bold">{props.author}</span>
{props.badge && (
<span className={`message-badge ${props.className} text-xs px-2 py-1 rounded uppercase tracking-wider font-bold`}>{props.badge}</span>
)}
<span className="message-time text-xs text-gray-500">{props.time}</span>
</div>
<div className="message-text leading-relaxed text-gray-300">{props.text}</div>
</div>
</div>
);
}

View file

@ -0,0 +1,16 @@
import React from "react";
export default function MessageInput() {
return (
<div className="flex items-center gap-2">
<button className="attachButton w-10 h-10 flex items-center justify-center rounded bg-[#1a1a1a] text-xl text-gray-400 mr-2">+</button>
<input
type="text"
className="message-input flex-1 bg-[#0f0f0f] border border-[#1a1a1a] rounded-lg px-4 py-3 text-gray-200 text-sm focus:outline-none focus:border-blue-500 placeholder:text-gray-500"
placeholder="Message #general (Foundation infrastructure channel)"
maxLength={2000}
/>
<button className="sendButton w-10 h-10 flex items-center justify-center rounded bg-blue-600 text-xl text-white ml-2">🎤</button>
</div>
);
}

View file

@ -0,0 +1,30 @@
import React 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" },
];
export default function ServerList() {
return (
<div className="server-list flex flex-col items-center py-3 gap-3 w-20 bg-[#0d0d0d] border-r border-[#1a1a1a]">
{servers.map((srv, i) =>
srv.id === "divider" ? (
<div key={i} className="server-divider w-10 h-0.5 bg-[#1a1a1a] my-1" />
) : (
<div
key={srv.id}
className={`server-icon ${srv.className} ${srv.active ? "active" : ""} w-14 h-14 rounded-xl flex items-center justify-center font-bold text-lg cursor-pointer transition-all relative`}
>
{srv.label}
</div>
)
)}
</div>
);
}

View file

@ -0,0 +1,57 @@
@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@300;400;500;700&display=swap');
html, body, #root {
height: 100%;
margin: 0;
padding: 0;
font-family: 'Roboto Mono', monospace;
background: #0a0a0a;
color: #e0e0e0;
}
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
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;
}
.connect-container {
height: 100vh;
display: flex;
}
.server-icon, .user-avatar, .member-avatar-small {
background: rgba(26,26,26,0.85);
backdrop-filter: blur(6px);
}
.channel-item.active, .channel-item:hover, .member-item:hover {
background: rgba(26,26,26,0.85);
backdrop-filter: blur(4px);
}
.message-input, .message-input-container {
background: rgba(15,15,15,0.95);
backdrop-filter: blur(4px);
}
::-webkit-scrollbar {
width: 8px;
background: #111;
}
::-webkit-scrollbar-thumb {
background: #222;
border-radius: 4px;
}

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AeThex Connect Mockup Web</title>
<link rel="stylesheet" href="./global.css" />
</head>
<body>
<div id="root"></div>
<script type="module" src="./index.jsx"></script>
</body>
</html>

View file

@ -0,0 +1,13 @@
import React from "react";
import { createRoot } from "react-dom/client";
import MainLayout from "./MainLayout";
import "./global.css";
const root = document.getElementById("root");
if (root) {
createRoot(root).render(
<React.StrictMode>
<MainLayout />
</React.StrictMode>
);
}