The header element was width:100% so the gradient filled relative to the viewport, not the visible text. With centered text, low fill percentages fell entirely outside the text bounds — making the first player (12.5%) invisible and causing shifts at different resolutions. Changed to width:fit-content with translateX(-50%) centering so the gradient maps 1:1 to the text content. Made-with: Cursor
923 lines
33 KiB
HTML
923 lines
33 KiB
HTML
<!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);
|
|
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;
|
|
}
|
|
|
|
#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;
|
|
}
|
|
</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>
|
|
|
|
<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>
|
|
|
|
<!-- 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>
|
|
|
|
<!-- 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>
|
|
</body>
|
|
</html>
|