Commit 93089ca1 authored by nextime's avatar nextime

update chat.html

parent b8a231ca
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Responsive Chat with Context Menus and Fixed Private Chat</title> <title>SHMCamStudio</title>
<style> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
@import url('https://fonts.googleapis.com/css2?family=Poppins&display=swap'); <style>
/* Reset and base styles */
/* Reset and base */ * {
* { margin: 0;
box-sizing: border-box; padding: 0;
} box-sizing: border-box;
body { font-family: 'Inter', Arial, sans-serif;
margin: 0; }
font-family: 'Poppins', sans-serif; body {
background: #121212; background: #0a1122;
color: #e0e0e0; color: #ffffff;
display: flex; display: flex;
min-height: 100vh; flex-direction: column;
user-select: none; min-height: 100vh;
-webkit-font-smoothing: antialiased; overflow: hidden;
-moz-osx-font-smoothing: grayscale; }
}
/* Top bar */
/* Container Layout */ .top-bar {
.container { height: 60px;
display: grid; background: linear-gradient(to right, #3b4d66, #6a1b9a);
grid-template-columns: 1fr 280px; display: flex;
grid-template-rows: 1fr; align-items: center;
gap: 0; padding: 0 20px;
width: 100%; gap: 10px;
height: 100vh; border-bottom: 2px solid #3b82f6;
background: #1f1f1f; }
overflow: hidden; .top-bar img {
} height: 40px;
width: auto;
/* User List Panel */ margin-right: 10px;
.user-list { }
background: #20232a; .top-bar span {
border-left: 1px solid #2e2e3a; font-size: 20px;
display: flex; font-weight: 600;
flex-direction: column; }
user-select: none; .top-bar a {
min-width: 280px; color: #3b82f6;
} text-decoration: none;
}
.user-list-header { .top-bar a:hover {
padding: 16px; text-decoration: underline;
font-weight: 700; }
font-size: 1.2rem;
border-bottom: 1px solid #2e2e3a; /* Desktop layout: 3 columns */
flex-shrink: 0; .container {
} display: flex;
height: calc(100vh - 60px);
.users { width: 100%;
flex-grow: 1; }
overflow-y: auto;
padding: 12px 8px; .left-column {
-webkit-overflow-scrolling: touch; width: 25%;
} min-width: 20%;
max-width: 45%;
.user-item { background: linear-gradient(to bottom, #2a2a2a, #1f1f1f);
padding: 10px 12px; padding: 10px;
margin-bottom: 8px; border-right: 2px solid #111111;
border-radius: 8px; height: 100%;
cursor: pointer; display: flex;
display: flex; flex-direction: column;
align-items: center; overflow-x: hidden;
gap: 12px; overflow-y: auto;
transition: background-color 0.3s ease; position: relative;
user-select: none; }
}
.user-item:hover, .resize-handle {
.user-item:focus { position: absolute;
background-color: #393d4d; right: -2px;
outline: none; top: 0;
} width: 4px;
.user-avatar { height: 100%;
flex-shrink: 0; background: #111111;
width: 32px; cursor: col-resize;
height: 32px; z-index: 10;
border-radius: 50%; }
background: linear-gradient(135deg, #6c5ce7, #0984e3);
color: white; .center-column {
font-weight: 700; flex: 1;
font-size: 1rem; padding: 10px;
display: flex; background: linear-gradient(to bottom, #1c1c1c, #121212);
align-items: center; display: flex;
justify-content: center; flex-direction: column;
user-select: none; }
}
.user-name { .right-column {
flex-grow: 1; flex: 0 0 20%;
font-size: 0.95rem; background: linear-gradient(to bottom, #2a2a2a, #1f1f1f);
color: #dcdde1; padding: 10px;
white-space: nowrap; border-left: 2px solid #111111;
overflow: hidden; overflow-y: auto;
text-overflow: ellipsis; }
user-select: text;
} /* Video window */
.user-status { .video-container {
width: 10px; position: relative;
height: 10px; background: #000;
border-radius: 50%; border-radius: 8px;
background-color: #44bd32; overflow: hidden;
box-shadow: 0 0 4px #44bd32aa; flex-shrink: 0;
flex-shrink: 0; width: 100%;
} }
video {
/* Main Chat Panel */ width: 100%;
.chat-panel { height: auto;
display: flex; aspect-ratio: 16 / 9;
flex-direction: column; display: block;
background: #181a20; }
user-select: none; .video-controls {
min-width: 0; position: absolute;
} top: 10px;
left: 10px;
.chat-header { display: flex;
padding: 16px 24px; gap: 10px;
border-bottom: 1px solid #2e2e3a; opacity: 0;
font-weight: 700; transition: opacity 0.3s;
font-size: 1.3rem; }
background: linear-gradient(135deg, #6c5ce7, #0984e3); .video-container:hover .video-controls {
color: white; opacity: 1;
user-select: none; }
flex-shrink: 0; .video-controls select, .video-controls button {
} padding: 5px;
border: none;
.chat-messages { border-radius: 20px;
flex-grow: 1; background: #3b82f6;
overflow-y: auto; color: #fff;
padding: 20px 24px; cursor: pointer;
scrollbar-width: thin; }
scrollbar-color: #394264 transparent; .video-controls select {
background: #121217; flex: 1;
user-select: text; min-width: 100px;
-webkit-overflow-scrolling: touch; max-width: 150px;
min-height: 0; }
} .video-controls button {
.chat-messages::-webkit-scrollbar { min-width: 60px;
width: 8px; }
} .video-stats {
.chat-messages::-webkit-scrollbar-thumb { position: absolute;
background-color: #394264; bottom: 0;
border-radius: 4px; width: 100%;
} background: rgba(0, 0, 0, 0.7);
padding: 5px;
.message { font-size: 12px;
max-width: 60%; border-radius: 0 0 8px 8px;
margin-bottom: 16px; }
padding: 12px 16px;
border-radius: 12px; /* Online/offline button */
line-height: 1.4; .status-button {
font-size: 0.95rem; width: 100%;
word-wrap: break-word; padding: 12px;
box-shadow: 0 2px 5px rgb(0 0 0 / 0.3); margin: 10px 0;
user-select: text; font-size: 18px;
display: flex; font-weight: 600;
align-items: center; border: none;
gap: 8px; border-radius: 20px;
} cursor: pointer;
.message.user { text-align: center;
background: linear-gradient(135deg, #0984e3, #6c5ce7); flex-shrink: 0;
color: white; }
align-self: flex-end; .status-button.online {
border-bottom-right-radius: 2px; background: #dc3545;
justify-content: flex-end; }
} .status-button.offline {
.message.other { background: #28a745;
background: #2d2f3a; }
color: #ddd; .status-menu {
align-self: flex-start; display: none;
border-bottom-left-radius: 2px; position: absolute;
} background: #2a2a2a;
.msg-username { border: 1px solid #3b82f6;
font-weight: 700; padding: 10px;
cursor: pointer; z-index: 100;
user-select: text; border-radius: 8px;
color: #a2d2ff; }
} .status-menu button {
display: block;
.chat-input-container { width: 100%;
display: flex; padding: 8px;
padding: 12px 24px; background: none;
border-top: 1px solid #2e2e3a; border: none;
background: #20232a; color: #fff;
align-items: center; text-align: left;
user-select: none; cursor: pointer;
flex-shrink: 0; border-radius: 20px;
} }
.chat-input { .status-menu button:hover {
flex-grow: 1; background: #3b82f6;
padding: 10px 16px; }
font-size: 1rem;
border-radius: 24px; /* Tabs */
border: none; .tabs {
background-color: #2d2f3a; display: flex;
color: #eee; border-bottom: 2px solid #111111;
outline: none; margin-top: 10px;
transition: background-color 0.2s ease; flex-shrink: 0;
} overflow-x: hidden;
.chat-input::placeholder { }
color: #888; .tab {
} padding: 8px 12px;
.chat-input:focus { cursor: pointer;
background-color: #424559; background: #2a2a2a;
} border-radius: 8px 8px 0 0;
.send-btn { margin-right: 5px;
margin-left: 12px; font-size: 14px;
background: linear-gradient(135deg, #6c5ce7, #0984e3); white-space: nowrap;
border: none; }
padding: 10px 18px; .tab.active {
color: white; background: #3b82f6;
font-weight: 700; }
font-size: 1rem; .tab-content {
border-radius: 24px; display: none;
cursor: pointer; padding: 15px;
user-select: none; background: #2a2a2a;
transition: background-color 0.3s ease; border-radius: 0 0 8px 8px;
} overflow-y: auto;
.send-btn:hover { flex-grow: 1;
background: linear-gradient(135deg, #5353c3, #0761c7); max-height: 100%;
} overflow-x: hidden;
}
/* Private Chat Popup */ .tab-content.active {
.private-chat-popup { display: block;
position: fixed; }
bottom: 16px;
right: 16px; /* Chat window */
width: 320px; .chat-window {
max-height: 480px; flex: 1;
background: #292b37; background: #121212;
border-radius: 12px; border-radius: 8px;
box-shadow: 0 8px 24px rgba(0,0,0,0.7); padding: 15px;
display: flex; overflow-y: auto;
flex-direction: column; margin-bottom: 10px;
overflow: hidden; }
z-index: 1000; .message {
font-size: 0.9rem; margin: 8px 0;
animation: fadeInUp 0.3s ease forwards; padding: 10px;
user-select: none; font-size: 16px;
resize: both; }
overflow: auto; .message.me {
} background: linear-gradient(to right, #2563eb, #4b0082);
.private-chat-header { margin-left: 20%;
background: linear-gradient(135deg, #6c5ce7, #0984e3); text-align: right;
padding: 12px 16px; border-radius: 10px 10px 0px 10px;
font-weight: 700; }
color: white; .message.me .sender {
display: flex; color: #93c5fd;
justify-content: space-between; font-weight: 600;
align-items: center; }
flex-shrink: 0; .message.me .content {
cursor: move; color: #f0f9ff;
} }
.private-chat-close { .message.other {
cursor: pointer; background: linear-gradient(to right, #1e293b, #2a1b3d);
font-weight: 700; margin-right: 20%;
font-size: 1.2rem; border-radius: 10px 10px 10px 0px;
line-height: 0; }
user-select: none; .message.notify-system {
transition: color 0.3s ease; background: linear-gradient(to right, #1f2937, #2a1b3d);
background: none; margin: 8px 10%;
border: none; text-align: center;
color: white; border-radius: 8px;
} }
.private-chat-close:hover { .message.notify-platform {
color: #ffd32a; background: linear-gradient(to right, #1f2937, #2a1b3d);
} margin: 8px 10%;
.private-chat-messages { text-align: center;
flex-grow: 1; border-radius: 8px;
padding: 12px 16px; }
overflow-y: auto; .message.tip {
background: #20212a; background: linear-gradient(to right, #064e3b, #1b3d2a);
scrollbar-width: thin; margin: 8px 10%;
scrollbar-color: #52526a transparent; text-align: center;
-webkit-overflow-scrolling: touch; border-radius: 8px 8px 0 8px;
user-select: text; }
min-height: 120px; .message.tip .content {
max-height: 320px; color: #d1fae5;
} }
.private-chat-messages::-webkit-scrollbar { .message .sender {
width: 6px; color: #60a5fa;
} font-weight: 600;
.private-chat-messages::-webkit-scrollbar-thumb { cursor: pointer;
background-color: #52526a; }
border-radius: 4px; .message .content {
} color: #a3bffa;
.private-message { }
margin-bottom: 12px; .message img {
background: #3a3c4d; max-width: 100px;
padding: 10px 14px; border-radius: 8px;
border-radius: 10px; }
color: #e4e6f0; .message a {
max-width: 90%; color: #00b7ff;
word-wrap: break-word; text-decoration: underline;
} }
.private-message.user { .chat-input-container {
background: linear-gradient(135deg, #0984e3, #6c5ce7); display: flex;
color: white; gap: 10px;
align-self: flex-end; align-items: stretch;
border-bottom-right-radius: 2px; }
} .chat-input {
.private-message.other { display: flex;
background: #3a3c4d; gap: 10px;
color: #ddd; position: relative;
align-self: flex-start; flex: 1;
border-bottom-left-radius: 2px; }
} .platform-selector {
.private-chat-input-container { height: 40px;
display: flex; padding: 8px;
padding: 10px 14px; border: none;
border-top: 1px solid #44475a; border-radius: 20px;
background: #2b2e42; background: #3b82f6;
align-items: center; color: #fff;
flex-shrink: 0; cursor: pointer;
} font-size: 14px;
.private-chat-input { width: 120px;
flex-grow: 1; }
padding: 8px 14px; .chat-input .emoji-button {
font-size: 0.95rem; position: absolute;
border-radius: 18px; left: 10px;
border: none; top: 50%;
background-color: #3d3f56; transform: translateY(-50%);
color: #eee; background: none;
outline: none; border: none;
transition: background-color 0.2s ease; font-size: 20px;
} cursor: pointer;
.private-chat-input::placeholder { z-index: 10;
color: #bbb; }
} .chat-input textarea {
.private-chat-input:focus { flex: 1;
background-color: #56577b; height: 40px;
} padding: 10px 40px 10px 60px;
.private-send-btn { border: none;
margin-left: 10px; border-radius: 20px;
background: linear-gradient(135deg, #6c5ce7, #0984e3); background: #1e293b;
border: none; color: #fff;
padding: 8px 20px; resize: none;
color: white; font-size: 14px;
font-weight: 700; }
font-size: 0.95rem; .chat-input button {
border-radius: 18px; height: 40px;
cursor: pointer; padding: 10px 20px;
user-select: none; background: #3b82f6;
transition: background-color 0.3s ease; border: none;
} border-radius: 20px;
.private-send-btn:hover { color: #fff;
background: linear-gradient(135deg, #5353c3, #0761c7); cursor: pointer;
} font-weight: 500;
}
@keyframes fadeInUp { .emoji-picker {
0% { display: none;
opacity: 0; position: absolute;
transform: translateY(20px); background: #2a2a2a;
} border: 1px solid #3b82f6;
100% { padding: 10px;
opacity: 1; z-index: 100;
transform: translateY(0); border-radius: 8px;
} max-width: 300px;
} flex-wrap: wrap;
}
/* Context Menu Styling */ .emoji-picker span {
.context-menu { cursor: pointer;
position: absolute; font-size: 20px;
background: #2a2c38; margin: 5px;
border-radius: 8px; }
padding: 8px 0;
width: 180px; /* User list */
box-shadow: 0 6px 15px rgba(0,0,0,0.4); .user-list-header {
z-index: 2000; display: flex;
display: none; justify-content: space-between;
user-select: none; padding: 8px;
} background: #2a2a2a;
.context-menu.visible { border-bottom: 2px solid #111111;
display: block; font-size: 16px;
} font-weight: 600;
.context-menu-item { }
padding: 10px 16px; .user-list .platform {
font-size: 0.9rem; margin: 10px 0;
color: #e0e0e0; }
cursor: pointer; .platform-header {
transition: background-color 0.25s ease; cursor: pointer;
} padding: 8px;
.context-menu-item:hover, background: #1e293b;
.context-menu-item:focus { border-radius: 8px;
background: #5353c3; display: flex;
color: white; justify-content: space-between;
outline: none; font-weight: 500;
} }
.user {
/* Responsive */ padding: 8px 10px;
@media (max-width: 768px) { display: flex;
body { justify-content: space-between;
display: block; align-items: center;
height: auto; font-size: 14px;
} }
.container { .status-dot {
height: auto; width: 10px;
display: flex; height: 10px;
flex-direction: column; border-radius: 50%;
} display: inline-block;
.chat-panel { margin-right: 5px;
order: 2; }
height: 70vh; .status-dot.online {
min-width: 100%; background: #28a745;
} }
.user-list { .status-dot.offline {
order: 1; background: #dc3545;
height: 30vh; }
min-width: 100%; .users {
border-left: none !important; display: none;
border-top: 1px solid #2e2e3a; }
} .users.active {
.chat-messages { display: block;
height: 100%; }
min-height: 0;
} /* Context menu */
.chat-input-container { .context-menu {
padding: 8px 16px; position: absolute;
} background: #2a2a2a;
.chat-input { border: 1px solid #3b82f6;
font-size: 0.9rem; padding: 10px;
padding: 8px 12px; z-index: 100;
} display: none;
.send-btn { border-radius: 8px;
padding: 8px 12px; }
font-size: 0.9rem; .context-menu a, .context-menu button {
} display: block;
.user-list-header, .chat-header { padding: 8px;
padding: 12px 16px; color: #fff;
font-size: 1.1rem; text-decoration: none;
} background: none;
.user-item { border: none;
padding: 8px 10px; cursor: pointer;
} width: 100%;
.user-avatar { text-align: left;
width: 28px; border-radius: 20px;
height: 28px; font-size: 14px;
font-size: 0.9rem; }
} .context-menu a:hover, .context-menu button:hover {
.user-name { background: #3b82f6;
font-size: 0.9rem; }
}
.private-chat-popup { /* Earnings table */
width: 90vw; .earnings-table {
max-height: 50vh; width: 100%;
bottom: 8px; max-width: 100%;
right: 5vw; margin: 0;
font-size: 0.85rem; border-collapse: collapse;
} font-size: 12px;
.private-chat-messages { }
max-height: 200px; .earnings-table th, .earnings-table td {
} padding: 6px;
} border: 1px solid #111111;
</style> text-align: left;
white-space: nowrap;
}
.earnings-table tr:nth-child(even) {
background: #1e293b;
}
.earnings-table th {
background: #3b82f6;
font-weight: 600;
}
.earnings-table .totals-row {
background: #4b5563;
font-weight: 600;
}
#earnings {
overflow-x: auto;
margin-bottom: 10px;
}
.reset-session-button {
width: 100%;
padding: 8px;
background: #dc3545;
border: none;
border-radius: 20px;
color: #fff;
cursor: pointer;
font-size: 12px;
text-align: center;
}
.reset-session-button:hover {
background: #b91c1c;
}
/* Mobile layout */
@media (max-width: 768px) {
.container {
flex-direction: column;
height: auto;
}
.left-column, .center-column, .right-column {
width: 100%;
border: none;
min-width: 0;
height: auto;
display: block;
}
.left-column {
order: -1;
}
.resize-handle {
display: none;
}
.chat-window {
height: 50vh;
}
.earnings-table {
width: 100%;
}
.tab-content {
max-height: none;
display: block;
}
video {
height: 200px;
}
.top-bar {
padding: 0 10px;
}
.top-bar img {
height: 30px;
}
.top-bar span {
font-size: 16px;
}
.chat-input-container {
flex-direction: column;
align-items: stretch;
}
.platform-selector {
width: 100%;
margin-bottom: 5px;
}
.chat-input {
width: 100%;
}
}
</style>
</head> </head>
<body> <body>
<div class="container"> <div class="top-bar">
<!-- Main Chat --> <img src="https://www.sexhack.me/content/uploads/2022/06/cropped-sexhack-300x99.png" alt="SexHack Logo">
<section class="chat-panel" aria-label="Public chat messages"> <span>SHM CamStudio by <a href="https://www.sexhack.me">sexhack.me</a></span>
<header class="chat-header">Global Chat</header> </div>
<div class="chat-messages" id="chatMessages" role="log" aria-live="polite" aria-relevant="additions"></div> <div class="container">
<form id="chatForm" class="chat-input-container" aria-label="Send message"> <div class="left-column">
<input type="text" id="chatInput" class="chat-input" placeholder="Enter message..." autocomplete="off" aria-required="true" /> <div class="resize-handle"></div>
<button type="submit" class="send-btn" aria-label="Send message">Send</button> <div class="video-container">
</form> <video id="video" autoplay></video>
</section> <div class="video-controls">
<select id="video-source"></select>
<!-- User List --> <button id="mirror-video">Mirror</button>
<aside class="user-list" aria-label="User list"> </div>
<div class="user-list-header">Online Users</div> <div class="video-stats">
<div class="users" id="userList" role="list" tabindex="0" aria-live="polite"></div> Bitrate: 2 Mbps | Quality: HD | Audio: <span id="audio-bar">████</span>
</aside> </div>
</div> </div>
<button class="status-button offline" id="status-button">GO ONLINE</button>
<!-- Private Chat Popup Template --> <div class="status-menu" id="status-menu">
<template id="privateChatTemplate"> <button data-platform="C4.sexhackme">C4.sexhackme: Offline</button>
<section class="private-chat-popup" role="dialog" aria-modal="true" aria-label="Private chat with user"> <button data-platform="SC.spora">SC.spora: Offline</button>
<header class="private-chat-header"> </div>
<span class="private-chat-user"></span> <div class="tabs">
<button class="private-chat-close" aria-label="Close private chat">&times;</button> <div class="tab active" data-tab="panel">Panel</div>
</header> <div class="tab" data-tab="earnings">Earnings</div>
<div class="private-chat-messages" role="log" aria-live="polite" aria-relevant="additions"></div> </div>
<form class="private-chat-input-container" aria-label="Send private message"> <div class="tab-content active" id="panel">
<input type="text" class="private-chat-input" placeholder="Enter private message..." autocomplete="off" aria-required="true" /> <iframe id="panel-iframe" style="width:100%;height:300px;border:none;border-radius:8px;"></iframe>
<button type="submit" class="private-send-btn" aria-label="Send private message">Send</button> </div>
</form> <div class="tab-content" id="earnings">
</section> <table class="earnings-table">
</template> <thead>
<tr><th>Plat.<br>Acc.</th><th>Last<br>Sess</th><th>Today</th><th>Hour</th><th>Sess</th></tr>
<!-- Context Menu --> </thead>
<nav id="contextMenu" class="context-menu" role="menu" aria-hidden="true" tabindex="-1" aria-label="User actions"> <tbody></tbody>
<div class="context-menu-item" data-action="openProfile" role="menuitem" tabindex="0">View Profile</div> </table>
<div class="context-menu-item" data-action="privateChat" role="menuitem" tabindex="0">Open Private Chat</div> <button class="reset-session-button" id="reset-session">Reset Session</button>
<div class="context-menu-item" data-action="ban" role="menuitem" tabindex="0">Ban User</div> </div>
<div class="context-menu-item" data-action="kick" role="menuitem" tabindex="0">Kick User</div> </div>
</nav> <div class="center-column">
<div class="chat-window" id="chat-window"></div>
<script> <div class="chat-input-container">
const users = [ <select class="platform-selector" id="platform-selector">
{ id: 'u1', name: 'Alice' }, <option value="all">All</option>
{ id: 'u2', name: 'Bob' }, <option value="C4.sexhackme">C4.sexhackme</option>
{ id: 'u3', name: 'Charlie' }, <option value="SC.spora">SC.spora</option>
{ id: 'u4', name: 'David' } </select>
]; <div class="chat-input">
<button class="emoji-button">😊</button>
const userListElem = document.getElementById('userList'); <textarea id="chat-input" rows="3" placeholder="Type a message..."></textarea>
const chatMessagesElem = document.getElementById('chatMessages'); <button id="send-message">Send</button>
const chatForm = document.getElementById('chatForm'); <div class="emoji-picker" id="emoji-picker">
const chatInput = document.getElementById('chatInput'); <span data-emoji="😊">😊</span><span data-emoji="👍">👍</span><span data-emoji="❤️">❤️</span><span data-emoji="😂">😂</span>
<span data-emoji="😍">😍</span><span data-emoji="😢">😢</span><span data-emoji="😎">😎</span><span data-emoji="😴">😴</span>
const privateChats = new Map(); <span data-emoji="😛">😛</span><span data-emoji="😣">😣</span><span data-emoji="😇">😇</span><span data-emoji="😱">😱</span>
<span data-emoji="😳">😳</span><span data-emoji="😬">😬</span><span data-emoji="🙂">🙂</span><span data-emoji="🙃">🙃</span>
const contextMenu = document.getElementById('contextMenu'); <span data-emoji="🙈">🙈</span><span data-emoji="🙉">🙉</span><span data-emoji="🙊">🙊</span><span data-emoji="💪">💪</span>
let contextMenuTargetUser = null; <span data-emoji="👏">👏</span><span data-emoji="👋">👋</span><span data-emoji="👌">👌</span><span data-emoji="👀">👀</span>
let contextMenuTargetType = null; <span data-emoji="💋">💋</span><span data-emoji="💕">💕</span><span data-emoji="💖">💖</span><span data-emoji="💗">💗</span>
<span data-emoji="💥">💥</span><span data-emoji="💦">💦</span><span data-emoji="🔥">🔥</span><span data-emoji="⭐"></span>
function renderUserList() { <span data-emoji="🌈">🌈</span><span data-emoji="🎉">🎉</span><span data-emoji="🎈">🎈</span><span data-emoji="🎁">🎁</span>
userListElem.innerHTML = ''; <span data-emoji="🍑">🍑</span><span data-emoji="🍆">🍆</span><span data-emoji="🍒">🍒</span><span data-emoji="🍓">🍓</span>
users.forEach(user => { </div>
const userItem = document.createElement('div'); </div>
userItem.className = 'user-item'; </div>
userItem.setAttribute('role', 'listitem'); </div>
userItem.tabIndex = 0; <div class="right-column">
userItem.dataset.userid = user.id; <div class="user-list-header">
<span>Userlist</span>
const avatar = document.createElement('div'); <span id="total-users">0</span>
avatar.className = 'user-avatar'; </div>
avatar.textContent = user.name.charAt(0).toUpperCase(); <div class="user-list" id="user-list"></div>
</div>
const name = document.createElement('div'); </div>
name.className = 'user-name'; <div class="context-menu" id="context-menu">
name.textContent = user.name; <a href="#" id="context-profile">View Profile</a>
<button id="context-tokens">Tokens: 0</button>
const status = document.createElement('div'); <button id="context-ban">Ban User</button>
status.className = 'user-status'; <button id="context-kick">Kick User</button>
status.title = 'Online'; <button id="context-private">Private Chat</button>
</div>
userItem.appendChild(avatar);
userItem.appendChild(name); <script>
userItem.appendChild(status); // Fake data for messages, users, earnings, status, and RTSP URLs
const fakeMessages = [
userItem.addEventListener('click', () => openPrivateChat(user)); { sender: "me", content: "Hello! 😊", timestamp: "2025-06-21 20:00" },
userItem.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openPrivateChat(user); } }); { sender: "alice@C4.sexhackme", content: "Hi! Check this: https://example.com", timestamp: "2025-06-21 20:01" },
{ sender: "bob@SC.spora", content: "<img src='https://pbs.twimg.com/media/FsypEu2X0AEtwK3?format=jpg&name=small' alt='Placeholder Image'>", timestamp: "2025-06-21 20:02" },
userItem.addEventListener('contextmenu', e => { { sender: "system", content: "system: *** Server maintenance scheduled at 1 AM", type: "notify-system", timestamp: "2025-06-21 20:03" },
e.preventDefault(); { sender: "platform@SC.spora", content: "platform: *** User joined the room", type: "notify-platform", timestamp: "2025-06-21 20:04" },
showContextMenu(e, user, 'userlist'); { sender: "bob@SC.spora", content: "<span class='sender' data-sender='bob@SC.spora' style='color: #10b981'>bob@SC.spora</span> TIPPED <b>50 TOKENS</b> ($5.00)<div style='text-align: center; color: #6ee7b7'>PM REQUEST</div>", type: "tip", timestamp: "2025-06-21 20:05" }
];
const fakeUsers = {
"C4.sexhackme": [
{ username: "alice", status: "online", tokens: 50 },
{ username: "charlie", status: "offline", tokens: 20 }
],
"SC.spora": [
{ username: "bob", status: "online", tokens: 30 }
]
};
const fakeEarnings = [
{ platform: "C4.sexhackme", lastSession: 100, today: 250, lastHour: 50, sess: 100 },
{ platform: "SC.spora", lastSession: 80, today: 200, lastHour: 30, sess: 80 }
];
const fakeStatus = {
"C4.sexhackme": "online",
"SC.spora": "offline"
};
const fakeRtspUrls = [
{ id: "rtsp1", url: "rtsp://example.com/stream1" },
{ id: "rtsp2", url: "rtsp://example.com/stream2" }
];
// Initialize video
const video = document.getElementById('video');
const videoSource = document.getElementById('video-source');
const mirrorVideo = document.getElementById('mirror-video');
let isMirrored = false;
async function populateVideoSources() {
videoSource.innerHTML = '<option value="none">Select Source</option>';
try {
const devices = await navigator.mediaDevices.enumerateDevices();
devices.filter(d => d.kind === 'videoinput').forEach((device, index) => {
const option = document.createElement('option');
option.value = device.deviceId;
option.text = device.label || `Webcam ${index + 1}`;
videoSource.appendChild(option);
});
} catch (err) {
console.error('Error enumerating devices:', err);
}
fakeRtspUrls.forEach(url => {
const option = document.createElement('option');
option.value = url.id;
option.text = url.url;
videoSource.appendChild(option);
});
navigator.mediaDevices.getUserMedia({ video: true })
.then(stream => {
video.srcObject = stream;
videoSource.value = stream.getVideoTracks()[0].getSettings().deviceId;
})
.catch(err => console.error('Webcam access error:', err));
}
videoSource.addEventListener('change', () => {
if (videoSource.value === 'none') {
video.srcObject = null;
video.src = '';
} else if (fakeRtspUrls.find(url => url.id === videoSource.value)) {
video.srcObject = null;
video.src = fakeRtspUrls.find(url => url.id === videoSource.value).url;
} else {
navigator.mediaDevices.getUserMedia({ video: { deviceId: videoSource.value } })
.then(stream => video.srcObject = stream)
.catch(err => console.error('Webcam error:', err));
}
});
mirrorVideo.addEventListener('click', () => {
isMirrored = !isMirrored;
video.style.transform = isMirrored ? 'scaleX(-1)' : 'scaleX(1)';
});
// Resize handle
const leftColumn = document.querySelector('.left-column');
const resizeHandle = document.querySelector('.resize-handle');
let isResizing = false;
resizeHandle.addEventListener('mousedown', (e) => {
isResizing = true;
e.preventDefault();
});
document.addEventListener('mousemove', (e) => {
if (isResizing) {
const newWidth = e.clientX;
if (newWidth >= window.innerWidth * 0.2 && newWidth <= window.innerWidth * 0.45) {
leftColumn.style.width = `${newWidth}px`;
}
}
});
document.addEventListener('mouseup', () => {
isResizing = false;
});
// Status button and menu
const statusButton = document.getElementById('status-button');
const statusMenu = document.getElementById('status-menu');
statusButton.addEventListener('click', (e) => {
statusMenu.style.display = statusMenu.style.display === 'block' ? 'none' : 'block';
statusMenu.style.left = `${e.pageX}px`;
statusMenu.style.top = `${e.pageY}px`;
});
statusMenu.querySelectorAll('button').forEach(btn => {
btn.addEventListener('click', () => {
const platform = btn.dataset.platform;
fakeStatus[platform] = fakeStatus[platform] === 'online' ? 'offline' : 'online';
updateStatus();
statusMenu.style.display = 'none';
});
}); });
userListElem.appendChild(userItem); function updateStatus() {
}); const isOnline = Object.values(fakeStatus).some(status => status === 'online');
} statusButton.className = `status-button ${isOnline ? 'online' : 'offline'}`;
statusButton.textContent = isOnline ? 'GO OFFLINE' : 'GO ONLINE';
function addPublicMessage(text, senderId = null, senderName = null) { statusMenu.querySelectorAll('button').forEach(btn => {
const msg = document.createElement('div'); const platform = btn.dataset.platform;
msg.className = senderId === "me" ? 'message user' : 'message other'; btn.textContent = `${platform}: ${fakeStatus[platform]}`;
});
const usernameSpan = document.createElement('span'); }
usernameSpan.className = 'msg-username';
if(!senderName) senderName = senderId ? (users.find(u => u.id === senderId)?.name || 'Someone') : 'Someone'; // Load panel content
if(!senderId) senderId = 'someone'; const panelIframe = document.getElementById('panel-iframe');
panelIframe.srcdoc = '<p>Control panel content loaded here.</p>';
usernameSpan.textContent = senderName;
usernameSpan.tabIndex = 0; // Chat functionality
usernameSpan.dataset.userid = senderId; const chatWindow = document.getElementById('chat-window');
const chatInput = document.getElementById('chat-input');
// Important: Use mousedown event to open context menu instantly on right click on usernames in main chat const sendButton = document.getElementById('send-message');
usernameSpan.addEventListener('contextmenu', e => { const emojiButton = document.querySelector('.emoji-button');
e.preventDefault(); const emojiPicker = document.getElementById('emoji-picker');
const user = users.find(u => u.id === senderId) || {id: 'someone', name: senderName}; const platformSelector = document.getElementById('platform-selector');
showContextMenu(e, user, 'chat');
}); function renderMessages(messages) {
usernameSpan.addEventListener('keydown', (e) => { chatWindow.innerHTML = '';
if ((e.shiftKey && e.key === "F10") || e.key === "ContextMenu") { messages.forEach(msg => {
e.preventDefault(); const div = document.createElement('div');
const user = users.find(u => u.id === senderId) || {id: 'someone', name: senderName}; div.className = `message ${msg.sender === 'me' ? 'me' : msg.type === 'notify-system' ? 'notify-system' : msg.type === 'notify-platform' ? 'notify-platform' : msg.type === 'tip' ? 'tip' : 'other'}`;
showContextMenu(e, user, 'chat'); div.dataset.sender = msg.sender;
} let content = msg.content;
}); if (!msg.type?.startsWith('notify') && msg.type !== 'tip' && !msg.content.includes('<img')) {
content = msg.content
msg.appendChild(usernameSpan); .replace(/\n/g, '<br>')
const textNode = document.createTextNode(`: ${text}`); .replace(/(https?:\/\/[^\s]+)/g, '<a href="$1" target="_blank">$1</a>');
msg.appendChild(textNode); }
if (msg.sender === 'me') {
chatMessagesElem.appendChild(msg); const senderSpan = document.createElement('span');
chatMessagesElem.scrollTop = chatMessagesElem.scrollHeight; senderSpan.className = 'sender';
} senderSpan.textContent = 'me:';
senderSpan.style.color = '#93c5fd';
chatForm.addEventListener('submit', e => { div.appendChild(senderSpan);
e.preventDefault(); div.appendChild(document.createTextNode(' '));
const msg = chatInput.value.trim(); } else if (msg.sender !== 'me' && !msg.type?.startsWith('notify') && msg.type !== 'tip') {
if (!msg) return; const senderSpan = document.createElement('span');
addPublicMessage(msg, "me", "You"); senderSpan.className = 'sender';
chatInput.value = ''; senderSpan.textContent = msg.sender;
chatInput.focus(); senderSpan.dataset.sender = msg.sender;
}); senderSpan.addEventListener('contextmenu', (e) => {
e.preventDefault();
function openPrivateChat(user) { showContextMenu(e, msg.sender);
if (privateChats.has(user.id)) { });
const chatPopup = privateChats.get(user.id); div.appendChild(senderSpan);
chatPopup.element.style.display = 'flex'; div.appendChild(document.createTextNode(': '));
chatPopup.input.focus(); }
return; const contentSpan = document.createElement('span');
} contentSpan.className = 'content';
contentSpan.innerHTML = content;
const template = document.getElementById('privateChatTemplate'); div.appendChild(contentSpan);
const clone = template.content.cloneNode(true); const senderSpan = div.querySelector('.sender');
if (senderSpan && msg.sender !== 'me' && !msg.type?.startsWith('notify')) {
const popup = clone.querySelector('.private-chat-popup'); senderSpan.addEventListener('contextmenu', (e) => {
const headerUser = clone.querySelector('.private-chat-user'); e.preventDefault();
const closeBtn = clone.querySelector('.private-chat-close'); showContextMenu(e, senderSpan.dataset.sender);
const msgsElem = clone.querySelector('.private-chat-messages'); });
const form = clone.querySelector('form'); }
const input = clone.querySelector('input'); chatWindow.appendChild(div);
});
headerUser.textContent = user.name; chatWindow.scrollTop = chatWindow.scrollHeight;
closeBtn.addEventListener('click', () => { }
popup.style.display = 'none';
}); // Username autocompletion
let completionState = { index: -1, prefix: '', original: '', matches: [] };
function addPrivateMessage(text, sender = 'other') {
const msg = document.createElement('div'); function getUsernames() {
msg.className = `private-message ${sender}`; const users = new Set();
msg.textContent = text; for (const platform in fakeUsers) {
msgsElem.appendChild(msg); fakeUsers[platform].forEach(user => {
msgsElem.scrollTop = msgsElem.scrollHeight; users.add(`${user.username}@${platform}`);
} });
}
form.addEventListener('submit', e => { fakeMessages.forEach(msg => {
e.preventDefault(); if (msg.sender !== 'me' && !msg.sender.startsWith('system') && !msg.sender.startsWith('platform@')) {
const message = input.value.trim(); users.add(msg.sender);
if (!message) return; }
addPrivateMessage(message, 'user'); });
input.value = ''; return Array.from(users);
input.focus(); }
});
chatInput.addEventListener('keydown', (e) => {
popup.style.display = 'flex'; if (e.key === 'Tab') {
document.body.appendChild(popup); e.preventDefault();
const text = chatInput.value;
privateChats.set(user.id, { const cursorPos = chatInput.selectionStart;
element: popup, const textBeforeCursor = text.slice(0, cursorPos);
input: input, const lastWordMatch = textBeforeCursor.match(/(\S+)$/);
addMessage: addPrivateMessage if (lastWordMatch) {
}); const prefix = lastWordMatch[1];
const usernames = getUsernames();
input.focus(); let matches = usernames.filter(u => u.toLowerCase().startsWith(prefix.toLowerCase()));
} if (matches.length === 0) return;
function showContextMenu(event, user, type) { if (completionState.prefix !== prefix) {
contextMenuTargetUser = user; completionState = { index: -1, prefix, original: prefix, matches };
contextMenuTargetType = type; }
const menuWidth = 180; completionState.index = (completionState.index + 1) % matches.length;
const menuHeight = 150; const selectedUsername = matches[completionState.index];
let x = event.clientX; const isFirstWord = textBeforeCursor.trim().length === prefix.length && textBeforeCursor.match(/^\s*/)[0].length + prefix.length === textBeforeCursor.length;
let y = event.clientY; const replacement = isFirstWord ? `${selectedUsername}: ` : selectedUsername;
if (x + menuWidth > window.innerWidth) { const newText = text.slice(0, cursorPos - prefix.length) + replacement + text.slice(cursorPos);
x = window.innerWidth - menuWidth - 8; chatInput.value = newText;
} chatInput.selectionStart = chatInput.selectionEnd = cursorPos - prefix.length + replacement.length;
if (y + menuHeight > window.innerHeight) { }
y = window.innerHeight - menuHeight - 8; } else if (e.key === 'Enter') {
} if (e.shiftKey || e.ctrlKey) {
e.preventDefault();
contextMenu.style.left = x + 'px'; chatInput.value += '\n';
contextMenu.style.top = y + 'px'; } else {
contextMenu.classList.add('visible'); e.preventDefault();
contextMenu.setAttribute('aria-hidden', 'false'); sendMessage();
}
const firstItem = contextMenu.querySelector('.context-menu-item'); } else {
if (firstItem) firstItem.focus(); completionState = { index: -1, prefix: '', original: '', matches: [] };
}
document.addEventListener('click', outsideClickHandler); });
document.addEventListener('keydown', keyboardHandler);
} sendButton.addEventListener('click', sendMessage);
function hideContextMenu() { function sendMessage() {
contextMenu.classList.remove('visible'); const content = chatInput.value.trim();
contextMenu.setAttribute('aria-hidden', 'true'); const platform = platformSelector.value;
contextMenuTargetUser = null; if (content) {
contextMenuTargetType = null; console.log(`Sending message to ${platform}: ${content}`);
document.removeEventListener('click', outsideClickHandler); fakeMessages.push({ sender: 'me', content, timestamp: new Date().toISOString() });
document.removeEventListener('keydown', keyboardHandler); renderMessages(fakeMessages);
} chatInput.value = '';
completionState = { index: -1, prefix: '', original: '', matches: [] };
function outsideClickHandler(event) { }
if (!contextMenu.contains(event.target)) { }
hideContextMenu();
} // Emoji picker
} emojiButton.addEventListener('mouseover', () => {
emojiPicker.style.display = 'flex';
function keyboardHandler(event) { emojiPicker.style.left = `${emojiButton.offsetLeft}px`;
if (!contextMenu.classList.contains('visible')) return; emojiPicker.style.top = `${emojiButton.offsetTop - emojiPicker.offsetHeight}px`;
});
const items = [...contextMenu.querySelectorAll('.context-menu-item')]; emojiPicker.addEventListener('mouseleave', () => {
const currentIndex = items.indexOf(document.activeElement); emojiPicker.style.display = 'none';
});
switch(event.key) { emojiPicker.querySelectorAll('span').forEach(span => {
case 'Escape': span.addEventListener('click', () => {
hideContextMenu(); chatInput.value += span.dataset.emoji;
break; emojiPicker.style.display = 'none';
case 'ArrowDown': chatInput.focus();
event.preventDefault(); });
const nextIndex = (currentIndex + 1) % items.length; });
items[nextIndex].focus();
break; // Context menu
case 'ArrowUp': const contextMenu = document.getElementById('context-menu');
event.preventDefault(); function showContextMenu(e, sender) {
const prevIndex = (currentIndex - 1 + items.length) % items.length; e.preventDefault();
items[prevIndex].focus(); const parts = sender.split('@');
break; if (parts.length !== 2) {
case 'Enter': console.error(`Invalid sender format: ${sender}`);
event.preventDefault(); return;
if (document.activeElement.classList.contains('context-menu-item')) { }
document.activeElement.click(); const [username, platformAccount] = parts;
} const [platform, account] = platformAccount.split('.');
break; if (!platform || !account) {
} console.error(`Invalid platform.account format: ${platformAccount}`);
} return;
}
contextMenu.addEventListener('click', e => { const userList = fakeUsers[`${platform}.${account}`];
if (!e.target.classList.contains('context-menu-item')) return; if (!userList) {
console.error(`Platform not found in fakeUsers: ${platform}.${account}`);
const action = e.target.dataset.action; return;
if (!contextMenuTargetUser) return; }
const user = userList.find(u => u.username === username);
switch (action) { if (!user) {
case 'openProfile': console.error(`User not found: ${username} in ${platform}.${account}`);
alert(`Open profile for ${contextMenuTargetUser.name} (ID: ${contextMenuTargetUser.id})`); return;
break; }
case 'privateChat': contextMenu.querySelector('#context-profile').href = `https://${platform}.com/profile/${username}`;
openPrivateChat(contextMenuTargetUser); contextMenu.querySelector('#context-tokens').textContent = `Tokens: ${user.tokens}`;
break; contextMenu.querySelector('#context-ban').onclick = () => alert(`Ban ${sender}`);
case 'ban': contextMenu.querySelector('#context-kick').onclick = () => alert(`Kick ${sender}`);
alert(`Ban user ${contextMenuTargetUser.name}`); contextMenu.querySelector('#context-private').onclick = () => alert(`Open private chat with ${sender}`);
break; contextMenu.style.display = 'block';
case 'kick': contextMenu.style.left = `${e.pageX}px`;
alert(`Kick user ${contextMenuTargetUser.name}`); contextMenu.style.top = `${e.pageY}px`;
break; }
default:
break; document.addEventListener('click', () => {
} contextMenu.style.display = 'none';
hideContextMenu(); statusMenu.style.display = 'none';
}); });
renderUserList(); // User list
addPublicMessage('Welcome to the chat!', 'someone', 'Someone'); function renderUserList() {
</script> const userList = document.getElementById('user-list');
const totalUsers = document.getElementById('total-users');
let total = 0;
userList.innerHTML = '';
for (const platform in fakeUsers) {
total += fakeUsers[platform].length;
const platformDiv = document.createElement('div');
platformDiv.className = 'platform';
platformDiv.innerHTML = `<div class="platform-header">${platform} <span>(${fakeUsers[platform].length})</span></div>`;
const usersDiv = document.createElement('div');
usersDiv.className = 'users';
fakeUsers[platform].forEach(user => {
const userDiv = document.createElement('div');
userDiv.className = 'user';
userDiv.dataset.sender = `${user.username}@${platform}`;
userDiv.innerHTML = `
<span><span class="status-dot ${user.status}"></span>${user.username}</span>
<span>${user.tokens} tokens</span>
`;
userDiv.addEventListener('contextmenu', (e) => {
e.preventDefault();
showContextMenu(e, userDiv.dataset.sender);
});
usersDiv.appendChild(userDiv);
});
platformDiv.appendChild(usersDiv);
userList.appendChild(platformDiv);
}
totalUsers.textContent = total;
document.querySelectorAll('.platform-header').forEach(header => {
header.addEventListener('click', () => {
const users = header.nextElementSibling;
users.classList.toggle('active');
});
});
}
// Tabs
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
tab.classList.add('active');
document.getElementById(tab.dataset.tab).classList.add('active');
});
});
// Earnings
function renderEarnings() {
const tbody = document.querySelector('.earnings-table tbody');
tbody.innerHTML = '';
const totals = fakeEarnings.reduce((acc, e) => ({
lastSession: acc.lastSession + e.lastSession,
today: acc.today + e.today,
lastHour: acc.lastHour + e.lastHour,
sess: acc.sess + e.sess
}), { lastSession: 0, today: 0, lastHour: 0, sess: 0 });
const totalsRow = document.createElement('tr');
totalsRow.className = 'totals-row';
totalsRow.innerHTML = `
<td>Totals</td>
<td>$${totals.lastSession}</td>
<td>$${totals.today}</td>
<td>$${totals.lastHour}</td>
<td>$${totals.sess}</td>
`;
tbody.appendChild(totalsRow);
fakeEarnings.forEach(e => {
const [platform, account] = e.platform.split('.');
const row = document.createElement('tr');
row.innerHTML = `
<td>${platform}<br>${account}</td>
<td>$${e.lastSession}</td>
<td>$${e.today}</td>
<td>$${e.lastHour}</td>
<td>$${e.sess}</td>
`;
tbody.appendChild(row);
});
}
// Reset session button
document.getElementById('reset-session').addEventListener('click', () => {
fakeEarnings.forEach(e => e.sess = 0);
renderEarnings();
});
// Initialize
populateVideoSources();
renderMessages(fakeMessages);
renderUserList();
renderEarnings();
updateStatus();
</script>
</body> </body>
</html> </html>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment