// Main ChatApp component — layout shared across all variants const Avatar = ({ chat, size = '' }) => (
{chat.initial} {chat.presence &&
}
); const ChatItem = ({ chat, onClick }) => (
{chat.name} {chat.time}
{chat.preview} {chat.unread && }
); const Reactions = ({ reactions }) => (
{reactions.map((r, i) => (
{r.emoji} {r.count}
))}
); const Bubble = ({ msg }) => (
{msg.text} {msg.time}{msg.read && msg.from === 'me' ? ' · Gelesen' : ''}
); const MediaBubble = ({ msg }) => (
{msg.label}
{msg.filename} {msg.size}
{msg.time}
); const EventPill = ({ evt }) => { const IconComp = evt.icon === 'phone' ? Icon.Phone : Icon.PhoneOff; return (
{evt.text} {evt.duration && · {evt.duration}} · {evt.time}
); }; const TypingIndicator = () => (
); const MessageItem = ({ msg, showAvatar, chat, myChat }) => { if (msg.type === 'event') return ; const wrapWithAvatar = (inner) => (
{msg.from === 'them' && (
{showAvatar && }
)}
{inner}
{msg.from === 'me' && (
{showAvatar && }
)}
); if (msg.type === 'typing') { return wrapWithAvatar(); } if (msg.type === 'media') { return wrapWithAvatar(); } return wrapWithAvatar( <> {msg.reactions && } ); }; const ProfileCard = ({ chat, position }) => (
{chat.initial}
{chat.name}
{chat.handle}
🟢 Online · seit 2h
📍 Beigetreten am 14. Mär 2026
🎮 Zockt gerade: Valorant
); const CallOverlay = ({ chat }) => (
Max ruft an…
Voice Call · klingelt
); const ChatApp = ({ variant, showCall, showProfile, isDark, onToggleDark }) => { const [activeChat, setActiveChat] = React.useState(CHATS[0]); const [activeTab, setActiveTab] = React.useState('chats'); // Call state: 'none' | 'incoming' | 'active' | 'pip' const [callScreen, setCallScreen] = React.useState('none'); const [callMode, setCallMode] = React.useState('grid'); // grid | focus | fullscreen const [focusedId, setFocusedId] = React.useState('max'); const [callState, setCallState] = React.useState({ muted: false, sharing: false, video: false, duration: '00:12:47' }); // Esc exits fullscreen React.useEffect(() => { const onKey = (e) => { if (e.key === 'Escape' && callScreen === 'active' && callMode === 'fullscreen') { setCallMode('grid'); } }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, [callScreen, callMode]); const chats = CHATS.map(c => ({...c, active: c.id === activeChat.id})); const messagesEndRef = React.useRef(null); React.useEffect(() => { if (messagesEndRef.current) { messagesEndRef.current.parentElement.scrollTop = messagesEndRef.current.parentElement.scrollHeight; } }, []); return (
ChatApp — {variant.label}
{variant.layout === 'rail' ? (
{/* Icon rail left */}
{/* Chatlist + user profile at bottom — only on chats tab */} {activeTab === 'chats' && (
Chats
{chats.map(c => ( setActiveChat(c)} /> ))}
dennis
● Online
)} {/* Chat area OR page content */}
{activeTab === 'friends' && } {activeTab === 'admin' && } {activeTab === 'settings' && } {activeTab === 'chats' && <> {!(callScreen === 'active' && callMode !== 'fullscreen') && (
{activeChat.name}
{activeChat.presence === 'online' ? '● Online' : activeChat.presence === 'idle' ? '● Abwesend' : activeChat.presence === 'dnd' ? '● Nicht stören' : '● Offline · zuletzt vor 2h'} {' · '}{activeChat.handle}
)} {/* Call dock — rendered above messages, Discord-style */} {callScreen === 'active' && callMode !== 'fullscreen' && (
setCallScreen('none')} callState={callState} setCallState={setCallState} />
)}
{MESSAGES.map((m, i) => { const next = MESSAGES[i+1]; const isLastOfRun = (m.from === 'them' || m.from === 'me') && (!next || next.from !== m.from || next.type === 'event'); const myChat = { initial: 'D', color: 'teal' }; return ; })}
}
) : ( <>
Netralax
dennis
@dennis
{variant.key === 'clean' && ( )}
{activeTab === 'friends' &&
} {activeTab === 'admin' &&
} {activeTab === 'settings' &&
} {activeTab === 'chats' && <>
Chats
{chats.map(c => ( setActiveChat(c)} /> ))}
{activeChat.name}
{activeChat.presence === 'online' ? '● Online' : activeChat.presence === 'idle' ? '● Abwesend' : activeChat.presence === 'dnd' ? '● Nicht stören' : '● Offline · zuletzt vor 2h'} {' · '}{activeChat.handle}
{MESSAGES.map((m, i) => { const next = MESSAGES[i+1]; const isLastOfRun = (m.from === 'them' || m.from === 'me') && (!next || next.from !== m.from || next.type === 'event'); const myChat = { initial: 'D', color: 'teal' }; return ; })}
}
)}
{/* Call screen switcher — only in Clean Rail, only on chats tab */} {variant.layout === 'rail' && activeTab === 'chats' && (
)} {/* Incoming call is a full overlay */} {callScreen === 'incoming' && (
setCallScreen('active')} onDecline={() => setCallScreen('none')} />
)} {/* Fullscreen call hides chat entirely */} {callScreen === 'active' && callMode === 'fullscreen' && (
setCallScreen('none')} callState={callState} setCallState={setCallState} />
)} {callScreen === 'pip' && ( setCallScreen('active')} onHangup={() => setCallScreen('none')} /> )} {showCall && callScreen === 'none' && }
); }; Object.assign(window, { ChatApp, Avatar, ChatItem, MessageItem, CallOverlay });