Files
OBS-overlay/optimized-controls.html

923 lines
33 KiB
HTML
Raw Permalink Normal View History

2026-02-07 12:24:54 -05:00
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Animated Stream Code Display</title>
<style>
body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: transparent;
font-family: 'Arial', sans-serif;
overflow: hidden;
}
#container {
text-align: center;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
position: relative;
}
#header {
position: absolute;
width: fit-content;
left: 50%;
transform: translateX(-50%) translateY(-220px);
2026-02-07 12:24:54 -05:00
color: #f35dcb;
font-size: 80px;
font-weight: bold;
letter-spacing: 2px;
opacity: 0;
transition: opacity 0.5s ease;
background: linear-gradient(to right, #ffffff 0%, #ffffff 0%, #f35dcb 0%, #f35dcb 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
filter: drop-shadow(3px 3px 8px rgba(0, 0, 0, 0.8));
}
@keyframes meter-full-pulse {
0% { filter: drop-shadow(3px 3px 8px rgba(0,0,0,0.8)); }
50% { filter: drop-shadow(0 0 20px rgba(255,255,255,0.6)) drop-shadow(3px 3px 8px rgba(0,0,0,0.8)); }
100% { filter: drop-shadow(3px 3px 8px rgba(0,0,0,0.8)); }
}
#header.meter-full-pulse {
animation: meter-full-pulse 1.2s ease-in-out infinite;
2026-02-07 12:24:54 -05:00
}
#footer {
position: absolute;
width: 100%;
text-align: center;
transform: translateY(160px);
color: #f35dcb;
font-size: 80px;
font-weight: bold;
text-shadow: 3px 3px 8px rgba(0, 0, 0, 0.8);
letter-spacing: 2px;
opacity: 0;
transition: opacity 0.5s ease;
}
.code-part {
font-size: 160px;
font-weight: bold;
color: #fdf935;
text-shadow: 4px 4px 12px rgba(0, 0, 0, 0.7);
letter-spacing: 8px;
position: absolute;
width: 100%;
text-align: center;
opacity: 0;
transition: opacity 0.5s ease;
}
#code-part1 {
transform: translateY(-100px);
}
#code-part2 {
transform: translateY(40px);
}
/* Redesigned controls */
#controls {
display: none;
position: absolute;
bottom: 50px;
right: 10px;
width: 280px; /* Narrower width */
padding: 10px;
background-color: rgba(0, 0, 0, 0.8);
border-radius: 8px;
z-index: 100;
max-height: 80vh;
overflow-y: auto;
}
.control-section {
margin-bottom: 5px;
border-bottom: 1px solid #555;
}
.section-header {
color: white;
background-color: rgba(0, 0, 0, 0.5);
padding: 5px;
margin: 0;
cursor: pointer;
font-size: 14px;
font-weight: bold;
display: flex;
justify-content: space-between;
align-items: center;
}
.section-content {
padding: 5px 0;
display: none;
}
.control-group {
margin-bottom: 8px;
display: flex;
flex-direction: column;
}
.control-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
}
label {
color: white;
font-size: 12px;
margin-right: 5px;
flex: 1;
}
input[type="text"] {
font-size: 14px;
padding: 3px;
text-align: center;
flex: 1;
text-transform: uppercase;
}
input[type="color"] {
width: 30px;
height: 20px;
vertical-align: middle;
}
input[type="number"] {
width: 50px;
font-size: 12px;
padding: 3px;
}
input[type="range"] {
width: 100px;
}
button {
font-size: 14px;
padding: 6px 12px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
margin-top: 5px;
width: 100%;
}
button:hover {
background-color: #45a049;
}
.action-buttons {
display: flex;
justify-content: space-between;
margin-top: 10px;
}
.action-buttons button {
width: 48%;
}
#show-controls-btn {
position: absolute;
bottom: 10px;
right: 10px;
background-color: rgba(0, 0, 0, 0.3);
color: #ffffff;
font-size: 12px;
padding: 4px 8px;
z-index: 100;
width: auto;
margin-top: 0;
}
#toggle-display-btn {
position: absolute;
bottom: 10px;
right: 120px;
background-color: rgba(0, 0, 0, 0.3);
color: #ffffff;
font-size: 12px;
padding: 4px 8px;
z-index: 100;
width: auto;
margin-top: 0;
}
/* Toggle indicator */
.toggle-indicator:after {
content: "▼";
margin-left: 5px;
font-size: 10px;
}
.toggle-indicator.active:after {
content: "▲";
}
/* Wide text fields */
.wide-field {
width: 100%;
}
#controls.bottom-position {
bottom: auto;
top: 80vh; /* Position below the footer */
left: 50%;
transform: translateX(-50%);
width: 80%;
max-width: 600px;
}
/* Style for active section */
.section-header.active {
background-color: rgba(79, 79, 79, 0.7);
}
/* Connection status indicator */
.status-row {
display: flex;
align-items: center;
margin-bottom: 5px;
gap: 6px;
}
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
flex-shrink: 0;
}
.status-dot.disconnected {
background-color: #888;
}
.status-dot.connecting {
background-color: #f0ad4e;
animation: pulse 1s infinite;
}
.status-dot.connected {
background-color: #4CAF50;
}
.status-dot.error {
background-color: #d9534f;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
.status-text {
color: #ccc;
font-size: 11px;
}
input[type="password"] {
font-size: 12px;
padding: 3px;
flex: 1;
width: 100%;
}
#ws-connect-btn {
background-color: #337ab7;
}
#ws-connect-btn:hover {
background-color: #286090;
}
#ws-disconnect-btn {
background-color: #d9534f;
}
#ws-disconnect-btn:hover {
background-color: #c9302c;
}
/* Player list */
#player-list-container {
position: absolute;
display: none;
flex-direction: column;
gap: 4px;
opacity: 0;
z-index: 10;
}
#player-list-container.player-list-position-left {
left: 24px;
top: 50%;
transform: translateY(-50%);
align-items: flex-start;
}
#player-list-container.player-list-position-right {
right: 24px;
top: 50%;
transform: translateY(-50%);
align-items: flex-start;
}
.player-slot {
display: flex;
gap: 8px;
align-items: baseline;
white-space: nowrap;
}
.player-slot-number {
color: rgba(255,255,255,0.5);
font-size: 24px;
font-weight: bold;
min-width: 2em;
text-align: right;
text-shadow: 2px 2px 6px rgba(0,0,0,0.7);
}
.player-slot-name {
font-size: 24px;
font-weight: bold;
text-shadow: 2px 2px 6px rgba(0,0,0,0.7);
}
.player-slot-name.empty {
color: transparent;
font-style: normal;
border-bottom: 2px dashed rgba(255,255,255,0.2);
min-width: 120px;
line-height: 0.8;
}
.player-slot-name.filled {
color: #ffffff;
border-bottom: none;
min-width: 0;
}
/* Debug dashboard */
#manager-dashboard .dashboard-grid {
display: grid;
grid-template-columns: auto 1fr;
gap: 2px 8px;
font-size: 11px;
color: #ccc;
}
#manager-dashboard .dashboard-grid .label {
color: #999;
text-align: right;
}
#manager-state {
display: inline-block;
padding: 2px 8px;
border-radius: 3px;
font-size: 11px;
font-weight: bold;
color: white;
background-color: #888;
}
.component-table {
width: 100%;
border-collapse: collapse;
font-size: 11px;
margin-top: 4px;
}
.component-table th,
.component-table td {
padding: 2px 4px;
text-align: left;
color: #ccc;
border-bottom: 1px solid #444;
}
.component-table th {
color: #999;
font-weight: normal;
}
.component-table select {
font-size: 10px;
background: #333;
color: #ccc;
border: 1px solid #555;
border-radius: 3px;
padding: 1px 2px;
}
#manager-event-log {
max-height: 100px;
overflow-y: auto;
font-size: 10px;
font-family: monospace;
background: rgba(0,0,0,0.3);
padding: 4px;
border-radius: 3px;
margin-top: 4px;
}
.event-log-entry {
color: #aaa;
line-height: 1.4;
}
.event-time {
color: #666;
}
.event-type {
color: #4fc3f7;
}
2026-02-07 12:24:54 -05:00
</style>
</head>
<body>
<div id="container">
<div id="header">ROOM CODE:</div>
<div id="code-part1" class="code-part"></div>
<div id="code-part2" class="code-part"></div>
<div id="footer">Enter @ jackbox.tv</div>
<!-- Add audio element -->
<audio id="theme-sound" preload="auto" loop>
<source src="https://feed.falsefinish.club/HSO/Audio/Room%20Code%20Theme.mp3" type="audio/mp3">
Your browser does not support the audio element.
</audio>
<div id="player-list-container"></div>
2026-02-07 12:24:54 -05:00
<div id="controls">
<!-- Code Settings Section -->
<div class="control-section">
<div class="section-header">
<span>Code Settings</span>
<span class="toggle-indicator active"></span>
</div>
<div class="section-content" style="display: block;">
<div class="control-group">
<div class="control-row">
<label>First Line:</label>
<input type="text" id="code1-input" maxlength="2" value="" style="width: 40px;">
<input type="color" id="color1-input" value="#fdf935">
</div>
<div class="control-row">
<label>Offset:</label>
<input type="number" id="offset1-input" value="-100" step="10">
</div>
</div>
<div class="control-group">
<div class="control-row">
<label>Second Line:</label>
<input type="text" id="code2-input" maxlength="2" value="" style="width: 40px;">
<input type="color" id="color2-input" value="#fdf935">
</div>
<div class="control-row">
<label>Offset:</label>
<input type="number" id="offset2-input" value="40" step="10">
</div>
</div>
<div class="control-row">
<label>Font Size:</label>
<input type="number" id="size-input" value="160" min="10" max="200" step="1">
</div>
<div class="control-row">
<label>Animation Cycle (sec):</label>
<input type="number" id="cycle-input" value="300" min="30" max="600" step="10">
</div>
</div>
</div>
<!-- Header Settings Section -->
<div class="control-section">
<div class="section-header">
<span>Header Settings</span>
<span class="toggle-indicator"></span>
</div>
<div class="section-content">
<div class="control-row">
<label>Text:</label>
<input type="text" id="header-text-input" value="ROOM CODE:" class="wide-field">
</div>
<div class="control-row">
<label>Color:</label>
<input type="color" id="header-color-input" value="#f35dcb">
</div>
<div class="control-row">
<label>Size:</label>
<input type="number" id="header-size-input" value="80" min="12" max="100" step="1">
</div>
<div class="control-row">
<label>Position:</label>
<input type="number" id="header-offset-input" value="-220" step="10">
</div>
</div>
</div>
<!-- Footer Settings Section -->
<div class="control-section">
<div class="section-header">
<span>Footer Settings</span>
<span class="toggle-indicator"></span>
</div>
<div class="section-content">
<div class="control-row">
<label>Text:</label>
<input type="text" id="footer-text-input" value="Enter @ jackbox.tv" class="wide-field">
</div>
<div class="control-row">
<label>Color:</label>
<input type="color" id="footer-color-input" value="#f35dcb">
</div>
<div class="control-row">
<label>Size:</label>
<input type="number" id="footer-size-input" value="80" min="12" max="100" step="1">
</div>
<div class="control-row">
<label>Position:</label>
<input type="number" id="footer-offset-input" value="160" step="10">
</div>
</div>
</div>
<!-- Timing Settings Section -->
<div class="control-section">
<div class="section-header">
<span>Timing Settings</span>
<span class="toggle-indicator"></span>
</div>
<div class="section-content">
<div class="control-group">
<div class="control-row">
<label>Header Appear (sec):</label>
<input type="number" id="header-appear-delay" value="10" min="0" max="30">
</div>
<div class="control-row">
<label>Duration:</label>
<input type="number" id="header-appear-duration" value="10" min="1" max="20">
</div>
</div>
<div class="control-group">
<div class="control-row">
<label>Header Hide (sec):</label>
<input type="number" id="header-hide-time" value="240" min="10" max="600">
</div>
<div class="control-row">
<label>Duration:</label>
<input type="number" id="header-hide-duration" value="60" min="1" max="120">
</div>
</div>
<div class="control-group">
<div class="control-row">
<label>Line 1 Appear (sec):</label>
<input type="number" id="line1-appear-delay" value="20" min="0" max="30">
</div>
<div class="control-row">
<label>Duration:</label>
<input type="number" id="line1-appear-duration" value="30" min="1" max="120">
</div>
</div>
<div class="control-group">
<div class="control-row">
<label>Line 1 Hide (sec):</label>
<input type="number" id="line1-hide-time" value="240" min="10" max="600">
</div>
<div class="control-row">
<label>Duration:</label>
<input type="number" id="line1-hide-duration" value="60" min="1" max="120">
</div>
</div>
<div class="control-group">
<div class="control-row">
<label>Line 2 Appear (sec):</label>
<input type="number" id="line2-appear-delay" value="40" min="0" max="300">
</div>
<div class="control-row">
<label>Duration:</label>
<input type="number" id="line2-appear-duration" value="60" min="1" max="120">
</div>
</div>
<div class="control-group">
<div class="control-row">
<label>Line 2 Hide (sec):</label>
<input type="number" id="line2-hide-time" value="240" min="10" max="600">
</div>
<div class="control-row">
<label>Duration:</label>
<input type="number" id="line2-hide-duration" value="60" min="1" max="120">
</div>
</div>
</div>
</div>
<!-- Audio Settings Section -->
<div class="control-section">
<div class="section-header">
<span>Audio Settings</span>
<span class="toggle-indicator"></span>
</div>
<div class="section-content">
<div class="control-row">
<label>Sound Effect:</label>
<input type="checkbox" id="sound-enabled" checked>
</div>
<div class="control-row">
<label>Volume:</label>
<input type="range" id="volume-slider" min="0" max="1" step="0.1" value="0.5">
<span id="volume-value">50%</span>
</div>
<div class="control-row">
<label>Sound URL:</label>
</div>
<div class="control-row">
<input type="text" id="sound-url-input" value="https://feed.falsefinish.club/HSO/Audio/Room%20Code%20Theme.mp3" class="wide-field">
</div>
<div class="control-row">
<button id="test-sound-btn">Test Sound</button>
</div>
</div>
</div>
<!-- Player List Settings Section -->
<div class="control-section">
<div class="section-header">
<span>Player List Settings</span>
<span class="toggle-indicator"></span>
</div>
<div class="section-content">
<div class="control-row">
<label>Enable Player List:</label>
<input type="checkbox" id="player-list-enabled">
</div>
<div class="control-row">
<label>Position:</label>
<select id="player-list-position">
<option value="left">Left Side</option>
<option value="right">Right Side</option>
</select>
</div>
<div class="control-row">
<label>Font Size:</label>
<input type="number" id="player-list-font-size" value="24" min="10" max="60" step="1">
</div>
<div class="control-row">
<label>Text Color:</label>
<input type="color" id="player-list-text-color" value="#ffffff">
</div>
<div class="control-row">
<label>Empty Slot Color:</label>
<input type="color" id="player-list-empty-color" value="#444444">
</div>
<div class="control-row">
<label>Vertical Offset:</label>
<input type="number" id="player-list-offset" value="0" step="10">
</div>
<div class="control-row">
<label>Horizontal Offset:</label>
<input type="number" id="player-list-offset-x" value="0" step="10">
</div>
</div>
</div>
2026-02-07 12:24:54 -05:00
<!-- Connection Settings Section -->
<div class="control-section">
<div class="section-header">
<span>Connection Settings</span>
<span class="toggle-indicator"></span>
</div>
<div class="section-content">
<div class="status-row">
<span class="status-dot disconnected" id="ws-status-dot"></span>
<span class="status-text" id="ws-status-text">Disconnected</span>
</div>
<div class="control-row">
<label>API URL:</label>
</div>
<div class="control-row">
<input type="text" id="api-url-input" placeholder="https://your-api-url" class="wide-field" style="text-transform: none;">
</div>
<div class="control-row">
<label>API Key:</label>
</div>
<div class="control-row">
<input type="password" id="api-key-input" placeholder="Enter API key">
</div>
<div class="control-row">
<button id="ws-connect-btn">Connect</button>
</div>
<div class="control-row" style="display: none;" id="ws-disconnect-row">
<button id="ws-disconnect-btn">Disconnect</button>
</div>
</div>
</div>
<!-- Overlay Manager Section -->
<div class="control-section" id="manager-dashboard">
<div class="section-header">
<span>Overlay Manager</span>
<span class="toggle-indicator"></span>
</div>
<div class="section-content">
<div class="dashboard-grid">
<span class="label">State:</span>
<span><span id="manager-state">IDLE</span></span>
<span class="label">Room:</span>
<span id="manager-room-code"></span>
<span class="label">Session:</span>
<span id="manager-session-id"></span>
<span class="label">Game:</span>
<span id="manager-game-title"></span>
<span class="label">Players:</span>
<span id="manager-player-count">0/?</span>
</div>
<table class="component-table">
<thead>
<tr><th>Component</th><th>Status</th><th>Override</th></tr>
</thead>
<tbody>
<tr>
<td>Room Code</td>
<td id="status-roomCode">Inactive</td>
<td><select id="override-roomCode"></select></td>
</tr>
<tr>
<td>Audio</td>
<td id="status-audio">Inactive</td>
<td><select id="override-audio"></select></td>
</tr>
<tr>
<td>Player List</td>
<td id="status-playerList">Inactive</td>
<td><select id="override-playerList"></select></td>
</tr>
</tbody>
</table>
<div id="manager-event-log"></div>
</div>
</div>
2026-02-07 12:24:54 -05:00
<!-- Position toggle -->
<div class="control-row" style="margin-top: 5px;">
<label>Position Controls Below:</label>
<input type="checkbox" id="position-toggle">
</div>
<!-- Action buttons -->
<div class="action-buttons">
<button id="update-btn">Update</button>
<button id="preview-btn">Preview</button>
</div>
</div>
</div>
<button id="toggle-display-btn">Hide Display</button>
<button id="show-controls-btn">Show Controls</button>
<script src="js/state-manager.js"></script>
<script src="js/websocket-client.js"></script>
<script src="js/room-code-display.js"></script>
<script src="js/audio-controller.js"></script>
<script src="js/player-list.js"></script>
<script src="js/controls.js"></script>
<script>
(function () {
var O = window.OBS;
var manager = new O.OverlayManager();
var roomCodeDisplay = new O.RoomCodeDisplay();
roomCodeDisplay.init(
{
header: document.getElementById('header'),
footer: document.getElementById('footer'),
codePart1: document.getElementById('code-part1'),
codePart2: document.getElementById('code-part2'),
},
{
code1: document.getElementById('code1-input'),
code2: document.getElementById('code2-input'),
color1: document.getElementById('color1-input'),
color2: document.getElementById('color2-input'),
offset1: document.getElementById('offset1-input'),
offset2: document.getElementById('offset2-input'),
size: document.getElementById('size-input'),
cycle: document.getElementById('cycle-input'),
headerText: document.getElementById('header-text-input'),
headerColor: document.getElementById('header-color-input'),
headerSize: document.getElementById('header-size-input'),
headerOffset: document.getElementById('header-offset-input'),
footerText: document.getElementById('footer-text-input'),
footerColor: document.getElementById('footer-color-input'),
footerSize: document.getElementById('footer-size-input'),
footerOffset: document.getElementById('footer-offset-input'),
headerAppearDelay: document.getElementById('header-appear-delay'),
headerAppearDuration: document.getElementById('header-appear-duration'),
headerHideTime: document.getElementById('header-hide-time'),
headerHideDuration: document.getElementById('header-hide-duration'),
line1AppearDelay: document.getElementById('line1-appear-delay'),
line1AppearDuration: document.getElementById('line1-appear-duration'),
line1HideTime: document.getElementById('line1-hide-time'),
line1HideDuration: document.getElementById('line1-hide-duration'),
line2AppearDelay: document.getElementById('line2-appear-delay'),
line2AppearDuration: document.getElementById('line2-appear-duration'),
line2HideTime: document.getElementById('line2-hide-time'),
line2HideDuration: document.getElementById('line2-hide-duration'),
}
);
manager.registerComponent('roomCode', roomCodeDisplay);
var audioController = new O.AudioController();
audioController.init(
document.getElementById('theme-sound'),
{
enabled: document.getElementById('sound-enabled'),
volume: document.getElementById('volume-slider'),
soundUrl: document.getElementById('sound-url-input'),
}
);
manager.registerComponent('audio', audioController);
var playerList = new O.PlayerList();
playerList.init(
document.getElementById('player-list-container'),
{
enabled: document.getElementById('player-list-enabled'),
position: document.getElementById('player-list-position'),
fontSize: document.getElementById('player-list-font-size'),
textColor: document.getElementById('player-list-text-color'),
emptyColor: document.getElementById('player-list-empty-color'),
offset: document.getElementById('player-list-offset'),
offsetX: document.getElementById('player-list-offset-x'),
},
{
headerAppearDelay: document.getElementById('header-appear-delay'),
headerAppearDuration: document.getElementById('header-appear-duration'),
}
);
manager.registerComponent('playerList', playerList);
var statusHandler = O.initConnectionStatusHandler();
var wsClient = new O.WebSocketClient({
onStatusChange: statusHandler,
onEvent: function (type, data) { manager.handleEvent(type, data); },
onSessionSubscribed: function (sessionId) {
console.log('[Overlay] Subscribed to session:', sessionId);
},
});
O.initControls(manager, wsClient, {
roomCode: roomCodeDisplay,
audio: audioController,
playerList: playerList,
});
var savedUrl = localStorage.getItem('jackbox-api-url');
var savedKey = localStorage.getItem('jackbox-api-key');
var apiUrlInput = document.getElementById('api-url-input');
var apiKeyInput = document.getElementById('api-key-input');
if (savedUrl && apiUrlInput) apiUrlInput.value = savedUrl;
if (savedKey && apiKeyInput) apiKeyInput.value = savedKey;
if (savedUrl && savedKey) {
setTimeout(function () { wsClient.connect(savedUrl, savedKey); }, 500);
}
window.__overlay = { manager: manager, wsClient: wsClient, roomCodeDisplay: roomCodeDisplay, audioController: audioController, playerList: playerList };
})();
</script>
2026-02-07 12:24:54 -05:00
</body>
</html>