Commit 93089ca1 authored by nextime's avatar nextime

update chat.html

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