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>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<style> <style>
@import url('https://fonts.googleapis.com/css2?family=Poppins&display=swap'); /* Reset and base styles */
/* Reset and base */
* { * {
margin: 0;
padding: 0;
box-sizing: border-box; box-sizing: border-box;
font-family: 'Inter', Arial, sans-serif;
} }
body { body {
margin: 0; background: #0a1122;
font-family: 'Poppins', sans-serif; color: #ffffff;
background: #121212;
color: #e0e0e0;
display: flex; display: flex;
flex-direction: column;
min-height: 100vh; min-height: 100vh;
user-select: none; overflow: hidden;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
} }
/* Container Layout */ /* 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 { .container {
display: grid; display: flex;
grid-template-columns: 1fr 280px; height: calc(100vh - 60px);
grid-template-rows: 1fr;
gap: 0;
width: 100%; width: 100%;
height: 100vh;
background: #1f1f1f;
overflow: hidden;
} }
/* User List Panel */ .left-column {
.user-list { width: 25%;
background: #20232a; min-width: 20%;
border-left: 1px solid #2e2e3a; max-width: 45%;
background: linear-gradient(to bottom, #2a2a2a, #1f1f1f);
padding: 10px;
border-right: 2px solid #111111;
height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
user-select: none; overflow-x: hidden;
min-width: 280px; overflow-y: auto;
position: relative;
} }
.user-list-header { .resize-handle {
padding: 16px; position: absolute;
font-weight: 700; right: -2px;
font-size: 1.2rem; top: 0;
border-bottom: 1px solid #2e2e3a; width: 4px;
flex-shrink: 0; height: 100%;
background: #111111;
cursor: col-resize;
z-index: 10;
} }
.users { .center-column {
flex-grow: 1; 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; overflow-y: auto;
padding: 12px 8px;
-webkit-overflow-scrolling: touch;
} }
.user-item { /* Video window */
padding: 10px 12px; .video-container {
margin-bottom: 8px; position: relative;
background: #000;
border-radius: 8px; border-radius: 8px;
cursor: pointer; overflow: hidden;
display: flex; flex-shrink: 0;
align-items: center; width: 100%;
gap: 12px;
transition: background-color 0.3s ease;
user-select: none;
} }
.user-item:hover, video {
.user-item:focus { width: 100%;
background-color: #393d4d; height: auto;
outline: none; aspect-ratio: 16 / 9;
display: block;
} }
.user-avatar { .video-controls {
flex-shrink: 0; position: absolute;
width: 32px; top: 10px;
height: 32px; left: 10px;
border-radius: 50%;
background: linear-gradient(135deg, #6c5ce7, #0984e3);
color: white;
font-weight: 700;
font-size: 1rem;
display: flex; display: flex;
align-items: center; gap: 10px;
justify-content: center; opacity: 0;
user-select: none; transition: opacity 0.3s;
} }
.user-name { .video-container:hover .video-controls {
flex-grow: 1; opacity: 1;
font-size: 0.95rem;
color: #dcdde1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
user-select: text;
} }
.user-status { .video-controls select, .video-controls button {
width: 10px; padding: 5px;
height: 10px; border: none;
border-radius: 50%; border-radius: 20px;
background-color: #44bd32; background: #3b82f6;
box-shadow: 0 0 4px #44bd32aa; color: #fff;
flex-shrink: 0; cursor: pointer;
} }
.video-controls select {
/* Main Chat Panel */ flex: 1;
.chat-panel { min-width: 100px;
display: flex; max-width: 150px;
flex-direction: column; }
background: #181a20; .video-controls button {
user-select: none; min-width: 60px;
min-width: 0; }
.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;
} }
.chat-header { /* Online/offline button */
padding: 16px 24px; .status-button {
border-bottom: 1px solid #2e2e3a; width: 100%;
font-weight: 700; padding: 12px;
font-size: 1.3rem; margin: 10px 0;
background: linear-gradient(135deg, #6c5ce7, #0984e3); font-size: 18px;
color: white; font-weight: 600;
user-select: none; border: none;
border-radius: 20px;
cursor: pointer;
text-align: center;
flex-shrink: 0; 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;
}
.chat-messages { /* Tabs */
flex-grow: 1; .tabs {
overflow-y: auto; display: flex;
padding: 20px 24px; border-bottom: 2px solid #111111;
scrollbar-width: thin; margin-top: 10px;
scrollbar-color: #394264 transparent; flex-shrink: 0;
background: #121217; overflow-x: hidden;
user-select: text;
-webkit-overflow-scrolling: touch;
min-height: 0;
} }
.chat-messages::-webkit-scrollbar { .tab {
width: 8px; padding: 8px 12px;
cursor: pointer;
background: #2a2a2a;
border-radius: 8px 8px 0 0;
margin-right: 5px;
font-size: 14px;
white-space: nowrap;
} }
.chat-messages::-webkit-scrollbar-thumb { .tab.active {
background-color: #394264; background: #3b82f6;
border-radius: 4px; }
.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 { .message {
max-width: 60%; margin: 8px 0;
margin-bottom: 16px; padding: 10px;
padding: 12px 16px; font-size: 16px;
border-radius: 12px; }
line-height: 1.4; .message.me {
font-size: 0.95rem; background: linear-gradient(to right, #2563eb, #4b0082);
word-wrap: break-word; margin-left: 20%;
box-shadow: 0 2px 5px rgb(0 0 0 / 0.3); text-align: right;
user-select: text; border-radius: 10px 10px 0px 10px;
display: flex; }
align-items: center; .message.me .sender {
gap: 8px; color: #93c5fd;
font-weight: 600;
} }
.message.user { .message.me .content {
background: linear-gradient(135deg, #0984e3, #6c5ce7); color: #f0f9ff;
color: white;
align-self: flex-end;
border-bottom-right-radius: 2px;
justify-content: flex-end;
} }
.message.other { .message.other {
background: #2d2f3a; background: linear-gradient(to right, #1e293b, #2a1b3d);
color: #ddd; margin-right: 20%;
align-self: flex-start; border-radius: 10px 10px 10px 0px;
border-bottom-left-radius: 2px; }
.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;
} }
.msg-username { .message.tip .content {
font-weight: 700; color: #d1fae5;
}
.message .sender {
color: #60a5fa;
font-weight: 600;
cursor: pointer; cursor: pointer;
user-select: text;
color: #a2d2ff;
} }
.message .content {
color: #a3bffa;
}
.message img {
max-width: 100px;
border-radius: 8px;
}
.message a {
color: #00b7ff;
text-decoration: underline;
}
.chat-input-container { .chat-input-container {
display: flex; display: flex;
padding: 12px 24px; gap: 10px;
border-top: 1px solid #2e2e3a; align-items: stretch;
background: #20232a;
align-items: center;
user-select: none;
flex-shrink: 0;
} }
.chat-input { .chat-input {
flex-grow: 1; display: flex;
padding: 10px 16px; gap: 10px;
font-size: 1rem; position: relative;
border-radius: 24px; flex: 1;
border: none;
background-color: #2d2f3a;
color: #eee;
outline: none;
transition: background-color 0.2s ease;
} }
.chat-input::placeholder { .platform-selector {
color: #888; height: 40px;
padding: 8px;
border: none;
border-radius: 20px;
background: #3b82f6;
color: #fff;
cursor: pointer;
font-size: 14px;
width: 120px;
} }
.chat-input:focus { .chat-input .emoji-button {
background-color: #424559; position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
font-size: 20px;
cursor: pointer;
z-index: 10;
} }
.send-btn { .chat-input textarea {
margin-left: 12px; flex: 1;
background: linear-gradient(135deg, #6c5ce7, #0984e3); 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: none;
padding: 10px 18px; border-radius: 20px;
color: white; color: #fff;
font-weight: 700;
font-size: 1rem;
border-radius: 24px;
cursor: pointer; cursor: pointer;
user-select: none; font-weight: 500;
transition: background-color 0.3s ease; }
} .emoji-picker {
.send-btn:hover { display: none;
background: linear-gradient(135deg, #5353c3, #0761c7); position: absolute;
} background: #2a2a2a;
border: 1px solid #3b82f6;
/* Private Chat Popup */ padding: 10px;
.private-chat-popup { z-index: 100;
position: fixed; border-radius: 8px;
bottom: 16px; max-width: 300px;
right: 16px; flex-wrap: wrap;
width: 320px; }
max-height: 480px; .emoji-picker span {
background: #292b37; cursor: pointer;
border-radius: 12px; font-size: 20px;
box-shadow: 0 8px 24px rgba(0,0,0,0.7); margin: 5px;
display: flex; }
flex-direction: column;
overflow: hidden; /* User list */
z-index: 1000; .user-list-header {
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; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; padding: 8px;
flex-shrink: 0; background: #2a2a2a;
cursor: move; border-bottom: 2px solid #111111;
font-size: 16px;
font-weight: 600;
} }
.private-chat-close { .user-list .platform {
cursor: pointer; margin: 10px 0;
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 { .platform-header {
color: #ffd32a; cursor: pointer;
padding: 8px;
background: #1e293b;
border-radius: 8px;
display: flex;
justify-content: space-between;
font-weight: 500;
} }
.private-chat-messages { .user {
flex-grow: 1; padding: 8px 10px;
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; display: flex;
padding: 10px 14px; justify-content: space-between;
border-top: 1px solid #44475a;
background: #2b2e42;
align-items: center; align-items: center;
flex-shrink: 0; font-size: 14px;
} }
.private-chat-input { .status-dot {
flex-grow: 1; width: 10px;
padding: 8px 14px; height: 10px;
font-size: 0.95rem; border-radius: 50%;
border-radius: 18px; display: inline-block;
border: none; margin-right: 5px;
background-color: #3d3f56; }
color: #eee; .status-dot.online {
outline: none; background: #28a745;
transition: background-color 0.2s ease;
} }
.private-chat-input::placeholder { .status-dot.offline {
color: #bbb; background: #dc3545;
} }
.private-chat-input:focus { .users {
background-color: #56577b; display: none;
} }
.private-send-btn { .users.active {
margin-left: 10px; display: block;
background: linear-gradient(135deg, #6c5ce7, #0984e3); }
/* 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; border: none;
padding: 8px 20px;
color: white;
font-weight: 700;
font-size: 0.95rem;
border-radius: 18px;
cursor: pointer; cursor: pointer;
user-select: none; width: 100%;
transition: background-color 0.3s ease; text-align: left;
border-radius: 20px;
font-size: 14px;
} }
.private-send-btn:hover { .context-menu a:hover, .context-menu button:hover {
background: linear-gradient(135deg, #5353c3, #0761c7); background: #3b82f6;
} }
@keyframes fadeInUp { /* Earnings table */
0% { .earnings-table {
opacity: 0; width: 100%;
transform: translateY(20px); max-width: 100%;
margin: 0;
border-collapse: collapse;
font-size: 12px;
} }
100% { .earnings-table th, .earnings-table td {
opacity: 1; padding: 6px;
transform: translateY(0); border: 1px solid #111111;
text-align: left;
white-space: nowrap;
} }
.earnings-table tr:nth-child(even) {
background: #1e293b;
} }
.earnings-table th {
/* Context Menu Styling */ background: #3b82f6;
.context-menu { font-weight: 600;
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 { .earnings-table .totals-row {
display: block; background: #4b5563;
font-weight: 600;
}
#earnings {
overflow-x: auto;
margin-bottom: 10px;
} }
.context-menu-item { .reset-session-button {
padding: 10px 16px; width: 100%;
font-size: 0.9rem; padding: 8px;
color: #e0e0e0; background: #dc3545;
border: none;
border-radius: 20px;
color: #fff;
cursor: pointer; cursor: pointer;
transition: background-color 0.25s ease; font-size: 12px;
text-align: center;
} }
.context-menu-item:hover, .reset-session-button:hover {
.context-menu-item:focus { background: #b91c1c;
background: #5353c3;
color: white;
outline: none;
} }
/* Responsive */ /* Mobile layout */
@media (max-width: 768px) { @media (max-width: 768px) {
body { .container {
display: block; flex-direction: column;
height: auto; height: auto;
} }
.container { .left-column, .center-column, .right-column {
width: 100%;
border: none;
min-width: 0;
height: auto; height: auto;
display: flex; display: block;
flex-direction: column;
} }
.chat-panel { .left-column {
order: 2; order: -1;
height: 70vh;
min-width: 100%;
} }
.user-list { .resize-handle {
order: 1; display: none;
height: 30vh;
min-width: 100%;
border-left: none !important;
border-top: 1px solid #2e2e3a;
} }
.chat-messages { .chat-window {
height: 100%; height: 50vh;
min-height: 0;
} }
.chat-input-container { .earnings-table {
padding: 8px 16px; width: 100%;
} }
.chat-input { .tab-content {
font-size: 0.9rem; max-height: none;
padding: 8px 12px; display: block;
} }
.send-btn { video {
padding: 8px 12px; height: 200px;
font-size: 0.9rem;
} }
.user-list-header, .chat-header { .top-bar {
padding: 12px 16px; padding: 0 10px;
font-size: 1.1rem;
} }
.user-item { .top-bar img {
padding: 8px 10px; height: 30px;
} }
.user-avatar { .top-bar span {
width: 28px; font-size: 16px;
height: 28px;
font-size: 0.9rem;
} }
.user-name { .chat-input-container {
font-size: 0.9rem; flex-direction: column;
align-items: stretch;
} }
.private-chat-popup { .platform-selector {
width: 90vw; width: 100%;
max-height: 50vh; margin-bottom: 5px;
bottom: 8px;
right: 5vw;
font-size: 0.85rem;
} }
.private-chat-messages { .chat-input {
max-height: 200px; width: 100%;
} }
} }
</style> </style>
</head> </head>
<body> <body>
<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="container">
<!-- Main Chat --> <div class="left-column">
<section class="chat-panel" aria-label="Public chat messages"> <div class="resize-handle"></div>
<header class="chat-header">Global Chat</header> <div class="video-container">
<div class="chat-messages" id="chatMessages" role="log" aria-live="polite" aria-relevant="additions"></div> <video id="video" autoplay></video>
<form id="chatForm" class="chat-input-container" aria-label="Send message"> <div class="video-controls">
<input type="text" id="chatInput" class="chat-input" placeholder="Enter message..." autocomplete="off" aria-required="true" /> <select id="video-source"></select>
<button type="submit" class="send-btn" aria-label="Send message">Send</button> <button id="mirror-video">Mirror</button>
</form> </div>
</section> <div class="video-stats">
Bitrate: 2 Mbps | Quality: HD | Audio: <span id="audio-bar">████</span>
<!-- User List --> </div>
<aside class="user-list" aria-label="User list"> </div>
<div class="user-list-header">Online Users</div> <button class="status-button offline" id="status-button">GO ONLINE</button>
<div class="users" id="userList" role="list" tabindex="0" aria-live="polite"></div> <div class="status-menu" id="status-menu">
</aside> <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> </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> <script>
const users = [ // Fake data for messages, users, earnings, status, and RTSP URLs
{ id: 'u1', name: 'Alice' }, const fakeMessages = [
{ id: 'u2', name: 'Bob' }, { sender: "me", content: "Hello! 😊", timestamp: "2025-06-21 20:00" },
{ id: 'u3', name: 'Charlie' }, { sender: "alice@C4.sexhackme", content: "Hi! Check this: https://example.com", timestamp: "2025-06-21 20:01" },
{ id: 'u4', name: 'David' } { 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" }
]; ];
const userListElem = document.getElementById('userList'); // Initialize video
const chatMessagesElem = document.getElementById('chatMessages'); const video = document.getElementById('video');
const chatForm = document.getElementById('chatForm'); const videoSource = document.getElementById('video-source');
const chatInput = document.getElementById('chatInput'); const mirrorVideo = document.getElementById('mirror-video');
let isMirrored = false;
const privateChats = new Map();
const contextMenu = document.getElementById('contextMenu');
let contextMenuTargetUser = null;
let contextMenuTargetType = null;
function renderUserList() { async function populateVideoSources() {
userListElem.innerHTML = ''; videoSource.innerHTML = '<option value="none">Select Source</option>';
users.forEach(user => { try {
const userItem = document.createElement('div'); const devices = await navigator.mediaDevices.enumerateDevices();
userItem.className = 'user-item'; devices.filter(d => d.kind === 'videoinput').forEach((device, index) => {
userItem.setAttribute('role', 'listitem'); const option = document.createElement('option');
userItem.tabIndex = 0; option.value = device.deviceId;
userItem.dataset.userid = user.id; option.text = device.label || `Webcam ${index + 1}`;
videoSource.appendChild(option);
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');
}); });
} catch (err) {
userListElem.appendChild(userItem); 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));
} }
function addPublicMessage(text, senderId = null, senderName = null) { videoSource.addEventListener('change', () => {
const msg = document.createElement('div'); if (videoSource.value === 'none') {
msg.className = senderId === "me" ? 'message user' : 'message other'; 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));
}
});
const usernameSpan = document.createElement('span'); mirrorVideo.addEventListener('click', () => {
usernameSpan.className = 'msg-username'; isMirrored = !isMirrored;
if(!senderName) senderName = senderId ? (users.find(u => u.id === senderId)?.name || 'Someone') : 'Someone'; video.style.transform = isMirrored ? 'scaleX(-1)' : 'scaleX(1)';
if(!senderId) senderId = 'someone'; });
usernameSpan.textContent = senderName; // Resize handle
usernameSpan.tabIndex = 0; const leftColumn = document.querySelector('.left-column');
usernameSpan.dataset.userid = senderId; const resizeHandle = document.querySelector('.resize-handle');
let isResizing = false;
// Important: Use mousedown event to open context menu instantly on right click on usernames in main chat resizeHandle.addEventListener('mousedown', (e) => {
usernameSpan.addEventListener('contextmenu', e => { isResizing = true;
e.preventDefault(); 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") { document.addEventListener('mousemove', (e) => {
e.preventDefault(); if (isResizing) {
const user = users.find(u => u.id === senderId) || {id: 'someone', name: senderName}; const newWidth = e.clientX;
showContextMenu(e, user, 'chat'); if (newWidth >= window.innerWidth * 0.2 && newWidth <= window.innerWidth * 0.45) {
leftColumn.style.width = `${newWidth}px`;
}
} }
}); });
msg.appendChild(usernameSpan); document.addEventListener('mouseup', () => {
const textNode = document.createTextNode(`: ${text}`); isResizing = false;
msg.appendChild(textNode); });
chatMessagesElem.appendChild(msg);
chatMessagesElem.scrollTop = chatMessagesElem.scrollHeight;
}
chatForm.addEventListener('submit', e => { // Status button and menu
e.preventDefault(); const statusButton = document.getElementById('status-button');
const msg = chatInput.value.trim(); const statusMenu = document.getElementById('status-menu');
if (!msg) return; statusButton.addEventListener('click', (e) => {
addPublicMessage(msg, "me", "You"); statusMenu.style.display = statusMenu.style.display === 'block' ? 'none' : 'block';
chatInput.value = ''; statusMenu.style.left = `${e.pageX}px`;
chatInput.focus(); 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';
});
}); });
function openPrivateChat(user) { function updateStatus() {
if (privateChats.has(user.id)) { const isOnline = Object.values(fakeStatus).some(status => status === 'online');
const chatPopup = privateChats.get(user.id); statusButton.className = `status-button ${isOnline ? 'online' : 'offline'}`;
chatPopup.element.style.display = 'flex'; statusButton.textContent = isOnline ? 'GO OFFLINE' : 'GO ONLINE';
chatPopup.input.focus(); statusMenu.querySelectorAll('button').forEach(btn => {
return; const platform = btn.dataset.platform;
btn.textContent = `${platform}: ${fakeStatus[platform]}`;
});
} }
const template = document.getElementById('privateChatTemplate'); // Load panel content
const clone = template.content.cloneNode(true); const panelIframe = document.getElementById('panel-iframe');
panelIframe.srcdoc = '<p>Control panel content loaded here.</p>';
const popup = clone.querySelector('.private-chat-popup'); // Chat functionality
const headerUser = clone.querySelector('.private-chat-user'); const chatWindow = document.getElementById('chat-window');
const closeBtn = clone.querySelector('.private-chat-close'); const chatInput = document.getElementById('chat-input');
const msgsElem = clone.querySelector('.private-chat-messages'); const sendButton = document.getElementById('send-message');
const form = clone.querySelector('form'); const emojiButton = document.querySelector('.emoji-button');
const input = clone.querySelector('input'); const emojiPicker = document.getElementById('emoji-picker');
const platformSelector = document.getElementById('platform-selector');
headerUser.textContent = user.name; function renderMessages(messages) {
closeBtn.addEventListener('click', () => { chatWindow.innerHTML = '';
popup.style.display = 'none'; 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);
function addPrivateMessage(text, sender = 'other') { div.appendChild(document.createTextNode(': '));
const msg = document.createElement('div'); }
msg.className = `private-message ${sender}`; const contentSpan = document.createElement('span');
msg.textContent = text; contentSpan.className = 'content';
msgsElem.appendChild(msg); contentSpan.innerHTML = content;
msgsElem.scrollTop = msgsElem.scrollHeight; div.appendChild(contentSpan);
} const senderSpan = div.querySelector('.sender');
if (senderSpan && msg.sender !== 'me' && !msg.type?.startsWith('notify')) {
form.addEventListener('submit', e => { senderSpan.addEventListener('contextmenu', (e) => {
e.preventDefault(); e.preventDefault();
const message = input.value.trim(); showContextMenu(e, senderSpan.dataset.sender);
if (!message) return;
addPrivateMessage(message, 'user');
input.value = '';
input.focus();
}); });
}
popup.style.display = 'flex'; chatWindow.appendChild(div);
document.body.appendChild(popup);
privateChats.set(user.id, {
element: popup,
input: input,
addMessage: addPrivateMessage
}); });
chatWindow.scrollTop = chatWindow.scrollHeight;
input.focus();
} }
function showContextMenu(event, user, type) { // Username autocompletion
contextMenuTargetUser = user; let completionState = { index: -1, prefix: '', original: '', matches: [] };
contextMenuTargetType = type;
const menuWidth = 180; function getUsernames() {
const menuHeight = 150; 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);
}
let x = event.clientX; chatInput.addEventListener('keydown', (e) => {
let y = event.clientY; 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 (x + menuWidth > window.innerWidth) { if (completionState.prefix !== prefix) {
x = window.innerWidth - menuWidth - 8; completionState = { index: -1, prefix, original: prefix, matches };
}
if (y + menuHeight > window.innerHeight) {
y = window.innerHeight - menuHeight - 8;
} }
contextMenu.style.left = x + 'px'; completionState.index = (completionState.index + 1) % matches.length;
contextMenu.style.top = y + 'px'; const selectedUsername = matches[completionState.index];
contextMenu.classList.add('visible');
contextMenu.setAttribute('aria-hidden', 'false');
const firstItem = contextMenu.querySelector('.context-menu-item'); const isFirstWord = textBeforeCursor.trim().length === prefix.length && textBeforeCursor.match(/^\s*/)[0].length + prefix.length === textBeforeCursor.length;
if (firstItem) firstItem.focus(); const replacement = isFirstWord ? `${selectedUsername}: ` : selectedUsername;
document.addEventListener('click', outsideClickHandler); const newText = text.slice(0, cursorPos - prefix.length) + replacement + text.slice(cursorPos);
document.addEventListener('keydown', keyboardHandler); chatInput.value = newText;
chatInput.selectionStart = chatInput.selectionEnd = cursorPos - prefix.length + replacement.length;
} }
} else if (e.key === 'Enter') {
function hideContextMenu() { if (e.shiftKey || e.ctrlKey) {
contextMenu.classList.remove('visible'); e.preventDefault();
contextMenu.setAttribute('aria-hidden', 'true'); chatInput.value += '\n';
contextMenuTargetUser = null; } else {
contextMenuTargetType = null; e.preventDefault();
document.removeEventListener('click', outsideClickHandler); sendMessage();
document.removeEventListener('keydown', keyboardHandler);
} }
} else {
completionState = { index: -1, prefix: '', original: '', matches: [] };
}
});
function outsideClickHandler(event) { sendButton.addEventListener('click', sendMessage);
if (!contextMenu.contains(event.target)) {
hideContextMenu(); 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: [] };
} }
} }
function keyboardHandler(event) { // Emoji picker
if (!contextMenu.classList.contains('visible')) return; emojiButton.addEventListener('mouseover', () => {
emojiPicker.style.display = 'flex';
const items = [...contextMenu.querySelectorAll('.context-menu-item')]; emojiPicker.style.left = `${emojiButton.offsetLeft}px`;
const currentIndex = items.indexOf(document.activeElement); 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();
});
});
switch(event.key) { // Context menu
case 'Escape': const contextMenu = document.getElementById('context-menu');
hideContextMenu(); function showContextMenu(e, sender) {
break; e.preventDefault();
case 'ArrowDown': const parts = sender.split('@');
event.preventDefault(); if (parts.length !== 2) {
const nextIndex = (currentIndex + 1) % items.length; console.error(`Invalid sender format: ${sender}`);
items[nextIndex].focus(); return;
break; }
case 'ArrowUp': const [username, platformAccount] = parts;
event.preventDefault(); const [platform, account] = platformAccount.split('.');
const prevIndex = (currentIndex - 1 + items.length) % items.length; if (!platform || !account) {
items[prevIndex].focus(); console.error(`Invalid platform.account format: ${platformAccount}`);
break; return;
case 'Enter':
event.preventDefault();
if (document.activeElement.classList.contains('context-menu-item')) {
document.activeElement.click();
} }
break; 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`;
} }
contextMenu.addEventListener('click', e => { document.addEventListener('click', () => {
if (!e.target.classList.contains('context-menu-item')) return; 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');
});
});
}
const action = e.target.dataset.action; // Tabs
if (!contextMenuTargetUser) return; 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');
});
});
switch (action) { // Earnings
case 'openProfile': function renderEarnings() {
alert(`Open profile for ${contextMenuTargetUser.name} (ID: ${contextMenuTargetUser.id})`); const tbody = document.querySelector('.earnings-table tbody');
break; tbody.innerHTML = '';
case 'privateChat': const totals = fakeEarnings.reduce((acc, e) => ({
openPrivateChat(contextMenuTargetUser); lastSession: acc.lastSession + e.lastSession,
break; today: acc.today + e.today,
case 'ban': lastHour: acc.lastHour + e.lastHour,
alert(`Ban user ${contextMenuTargetUser.name}`); sess: acc.sess + e.sess
break; }), { lastSession: 0, today: 0, lastHour: 0, sess: 0 });
case 'kick': const totalsRow = document.createElement('tr');
alert(`Kick user ${contextMenuTargetUser.name}`); totalsRow.className = 'totals-row';
break; totalsRow.innerHTML = `
default: <td>Totals</td>
break; <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);
});
} }
hideContextMenu();
// Reset session button
document.getElementById('reset-session').addEventListener('click', () => {
fakeEarnings.forEach(e => e.sess = 0);
renderEarnings();
}); });
// Initialize
populateVideoSources();
renderMessages(fakeMessages);
renderUserList(); renderUserList();
addPublicMessage('Welcome to the chat!', 'someone', 'Someone'); renderEarnings();
updateStatus();
</script> </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