Files
webcounter/templates/mechanical_counter_controller.html

1516 lines
52 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mechanical Counter - Controller</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.2/socket.io.js"></script>
<style>
@font-face {
font-family: 'OpenGostTypeB';
src: url('/static/fonts/OpenGost Type B/OpenGostTypeB.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'OpenGostTypeB', monospace;
background: linear-gradient(135deg, #1e3c72, #2a5298);
color: #fff;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
background: rgba(0, 0, 0, 0.3);
border-radius: 15px;
padding: 30px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1);
}
h1 {
text-align: center;
margin-bottom: 30px;
color: #00ff00;
text-shadow: 0 0 10px #00ff00;
font-size: 2.5em;
}
.status {
text-align: center;
padding: 10px;
border-radius: 5px;
margin-bottom: 30px;
font-weight: bold;
}
.status.connected {
background-color: rgba(0, 255, 0, 0.2);
color: #00ff00;
border: 1px solid #00ff00;
}
.status.disconnected {
background-color: rgba(255, 0, 0, 0.2);
color: #ff0000;
border: 1px solid #ff0000;
}
.counter-inputs {
display: flex;
justify-content: center;
gap: 15px;
margin-bottom: 30px;
flex-wrap: wrap;
}
.digit-input-group {
text-align: center;
}
.digit-label {
display: block;
margin-bottom: 10px;
font-size: 1.2em;
color: #00ff00;
text-shadow: 0 0 5px #00ff00;
}
.digit-input-group {
text-align: center;
position: relative;
}
.digit-input {
width: 80px;
height: 80px;
font-size: 2.5em;
text-align: center;
background: rgba(0, 0, 0, 0.5);
border: 2px solid #00ff00;
border-radius: 10px;
color: #00ff00;
font-family: 'OpenGostTypeB', monospace;
text-shadow: 0 0 10px #00ff00;
box-shadow: 0 0 20px rgba(0, 255, 0, 0.3);
transition: all 0.3s ease;
}
.digit-buttons {
display: flex;
flex-direction: column;
gap: 5px;
margin-top: 10px;
}
.digit-btn {
width: 40px;
height: 30px;
font-size: 1.2em;
border: none;
border-radius: 5px;
cursor: pointer;
font-family: 'OpenGostTypeB', monospace;
font-weight: bold;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
}
.digit-btn-up {
background: linear-gradient(45deg, #00ff00, #00cc00);
color: #000;
box-shadow: 0 2px 8px rgba(0, 255, 0, 0.3);
}
.digit-btn-up:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 255, 0, 0.5);
}
.digit-btn-down {
background: linear-gradient(45deg, #ff6b6b, #ee5a52);
color: #fff;
box-shadow: 0 2px 8px rgba(255, 107, 107, 0.3);
}
.digit-btn-down:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(255, 107, 107, 0.5);
}
.digit-btn:active {
transform: translateY(0);
}
.digit-input:focus {
outline: none;
border-color: #00ffff;
box-shadow: 0 0 30px rgba(0, 255, 255, 0.5);
transform: scale(1.05);
}
.digit-input::-webkit-outer-spin-button,
.digit-input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
.digit-input[type=number] {
-moz-appearance: textfield;
}
.controls {
display: flex;
gap: 15px;
justify-content: center;
margin-bottom: 30px;
flex-wrap: wrap;
}
.btn {
padding: 15px 30px;
font-size: 1.1em;
border: none;
border-radius: 8px;
cursor: pointer;
font-family: 'OpenGostTypeB', monospace;
font-weight: bold;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 1px;
}
.btn-primary {
background: linear-gradient(45deg, #00ff00, #00cc00);
color: #000;
box-shadow: 0 4px 15px rgba(0, 255, 0, 0.3);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0, 255, 0, 0.5);
}
.btn-secondary {
background: linear-gradient(45deg, #ff6b6b, #ee5a52);
color: #fff;
box-shadow: 0 4px 15px rgba(255, 107, 107, 0.3);
}
.btn-secondary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(255, 107, 107, 0.5);
}
.btn-warning {
background: linear-gradient(45deg, #ffa726, #ff9800);
color: #000;
box-shadow: 0 4px 15px rgba(255, 167, 38, 0.3);
}
.btn-warning:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(255, 167, 38, 0.5);
}
.quick-actions {
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 20px;
margin-bottom: 30px;
}
.quick-actions h3 {
text-align: center;
margin-bottom: 20px;
color: #00ff00;
text-shadow: 0 0 5px #00ff00;
}
.quick-buttons {
display: flex;
gap: 10px;
justify-content: center;
flex-wrap: wrap;
}
.quick-btn {
padding: 10px 20px;
font-size: 0.9em;
border: none;
border-radius: 5px;
cursor: pointer;
font-family: 'OpenGostTypeB', monospace;
transition: all 0.3s ease;
}
.quick-btn:hover {
transform: scale(1.05);
}
.sent-events {
background: rgba(0, 0, 0, 0.3);
border-radius: 10px;
padding: 20px;
max-height: 300px;
overflow-y: auto;
}
.sent-events h3 {
text-align: center;
margin-bottom: 15px;
color: #00ff00;
}
.sent-event {
background: rgba(255, 255, 255, 0.1);
padding: 10px;
margin-bottom: 10px;
border-radius: 5px;
border-left: 4px solid #00ff00;
}
.sent-event-time {
color: #ccc;
font-size: 0.8em;
margin-bottom: 5px;
}
.sent-event-data {
font-family: monospace;
background: rgba(0, 0, 0, 0.3);
padding: 5px;
border-radius: 3px;
word-break: break-all;
}
.nav-link {
display: inline-block;
margin-top: 20px;
padding: 10px 20px;
background: rgba(255, 255, 255, 0.2);
color: #fff;
text-decoration: none;
border-radius: 5px;
transition: all 0.3s ease;
}
.nav-link:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
}
.position-controls {
background: rgba(0, 0, 0, 0.5);
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
border: 1px solid #00ff00;
}
.position-controls h3 {
color: #00ff00;
text-align: center;
margin-bottom: 15px;
}
.digit-position-group {
display: flex;
align-items: center;
gap: 15px;
margin-bottom: 15px;
padding: 10px;
background: rgba(255, 255, 255, 0.1);
border-radius: 5px;
}
.digit-position-label {
min-width: 80px;
font-weight: bold;
color: #00ff00;
}
.position-slider-group {
display: flex;
align-items: center;
gap: 10px;
flex: 1;
}
.position-slider {
flex: 1;
height: 6px;
background: rgba(255, 255, 255, 0.2);
border-radius: 3px;
outline: none;
-webkit-appearance: none;
}
.position-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px;
height: 16px;
background: #00ff00;
border-radius: 50%;
cursor: pointer;
}
.position-slider::-moz-range-thumb {
width: 16px;
height: 16px;
background: #00ff00;
border-radius: 50%;
cursor: pointer;
border: none;
}
.position-value {
min-width: 50px;
text-align: center;
font-family: monospace;
color: #00ff00;
}
.font-size-controls {
background: rgba(0, 0, 0, 0.5);
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
border: 1px solid #00ff00;
}
.font-size-controls h3 {
color: #00ff00;
text-align: center;
margin-bottom: 15px;
}
.font-size-group {
display: flex;
align-items: center;
gap: 15px;
justify-content: center;
margin-bottom: 15px;
}
.font-size-label {
font-weight: bold;
color: #00ff00;
min-width: 100px;
}
.font-size-slider {
flex: 1;
max-width: 300px;
height: 6px;
background: rgba(255, 255, 255, 0.2);
border-radius: 3px;
outline: none;
-webkit-appearance: none;
}
.font-size-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px;
height: 16px;
background: #00ff00;
border-radius: 50%;
cursor: pointer;
}
.font-size-slider::-moz-range-thumb {
width: 16px;
height: 16px;
background: #00ff00;
border-radius: 50%;
cursor: pointer;
border: none;
}
.font-size-value {
min-width: 60px;
text-align: center;
font-family: monospace;
color: #00ff00;
font-weight: bold;
}
.font-size-preview {
display: inline-block;
padding: 10px;
background: rgba(0, 0, 0, 0.8);
border: 1px solid #00ff00;
border-radius: 5px;
margin-left: 15px;
}
.wheel-size-controls {
background: rgba(0, 0, 0, 0.5);
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
border: 1px solid #00ff00;
}
.wheel-size-controls h3 {
color: #00ff00;
text-align: center;
margin-bottom: 15px;
}
.wheel-size-group {
display: flex;
align-items: center;
gap: 15px;
justify-content: center;
margin-bottom: 15px;
}
.wheel-size-label {
font-weight: bold;
color: #00ff00;
min-width: 80px;
}
.wheel-size-slider {
flex: 1;
max-width: 200px;
height: 6px;
background: rgba(255, 255, 255, 0.2);
border-radius: 3px;
outline: none;
-webkit-appearance: none;
}
.wheel-size-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px;
height: 16px;
background: #00ff00;
border-radius: 50%;
cursor: pointer;
}
.wheel-size-slider::-moz-range-thumb {
width: 16px;
height: 16px;
background: #00ff00;
border-radius: 50%;
cursor: pointer;
border: none;
}
.wheel-size-value {
min-width: 60px;
text-align: center;
font-family: monospace;
color: #00ff00;
font-weight: bold;
}
.wheel-size-preview {
display: inline-block;
padding: 10px;
background: rgba(0, 0, 0, 0.8);
border: 1px solid #00ff00;
border-radius: 5px;
margin-left: 15px;
text-align: center;
}
.wheel-size-preview-box {
background: linear-gradient(145deg, #2a2a2a, #1a1a1a);
border: 1px solid #444;
border-radius: 5px;
display: inline-block;
}
.animation-speed-controls {
background: rgba(0, 0, 0, 0.5);
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
border: 1px solid #00ff00;
}
.animation-speed-controls h3 {
color: #00ff00;
text-align: center;
margin-bottom: 15px;
}
.animation-speed-group {
display: flex;
align-items: center;
gap: 15px;
justify-content: center;
margin-bottom: 15px;
}
.animation-speed-label {
font-weight: bold;
color: #00ff00;
min-width: 120px;
}
.animation-speed-slider {
flex: 1;
max-width: 300px;
height: 6px;
background: rgba(255, 255, 255, 0.2);
border-radius: 3px;
outline: none;
-webkit-appearance: none;
}
.animation-speed-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px;
height: 16px;
background: #00ff00;
border-radius: 50%;
cursor: pointer;
}
.animation-speed-slider::-moz-range-thumb {
width: 16px;
height: 16px;
background: #00ff00;
border-radius: 50%;
cursor: pointer;
border: none;
}
.animation-speed-value {
min-width: 80px;
text-align: center;
font-family: monospace;
color: #00ff00;
font-weight: bold;
}
.animation-speed-preview {
display: inline-block;
padding: 10px;
background: rgba(0, 0, 0, 0.8);
border: 1px solid #00ff00;
border-radius: 5px;
margin-left: 15px;
text-align: center;
}
.animation-speed-preview-box {
width: 40px;
height: 60px;
background: linear-gradient(145deg, #2a2a2a, #1a1a1a);
border: 1px solid #444;
border-radius: 5px;
display: inline-block;
position: relative;
overflow: hidden;
}
.animation-speed-preview-strip {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 600px;
transition: transform 0.5s ease;
display: flex;
flex-direction: column;
}
.animation-speed-preview-digit {
width: 100%;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
font-size: 20px;
border-bottom: 1px solid #333;
}
.brightness-controls {
background: rgba(0, 0, 0, 0.5);
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
border: 1px solid #00ff00;
}
.brightness-controls h3 {
color: #00ff00;
text-align: center;
margin-bottom: 15px;
}
.brightness-group {
display: flex;
align-items: center;
gap: 15px;
justify-content: center;
margin-bottom: 15px;
}
.brightness-label {
font-weight: bold;
color: #00ff00;
min-width: 120px;
}
.brightness-slider {
flex: 1;
max-width: 300px;
height: 6px;
background: rgba(255, 255, 255, 0.2);
border-radius: 3px;
outline: none;
-webkit-appearance: none;
}
.brightness-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px;
height: 16px;
background: #00ff00;
border-radius: 50%;
cursor: pointer;
}
.brightness-slider::-moz-range-thumb {
width: 16px;
height: 16px;
background: #00ff00;
border-radius: 50%;
cursor: pointer;
border: none;
}
.brightness-value {
min-width: 80px;
text-align: center;
font-family: monospace;
color: #00ff00;
font-weight: bold;
}
.brightness-preview {
display: inline-block;
padding: 10px;
background: rgba(0, 0, 0, 0.8);
border: 1px solid #00ff00;
border-radius: 5px;
margin-left: 15px;
text-align: center;
}
.brightness-preview-box {
width: 40px;
height: 60px;
background: linear-gradient(145deg, #2a2a2a, #1a1a1a);
border: 1px solid #444;
border-radius: 5px;
display: inline-block;
position: relative;
overflow: hidden;
}
.brightness-preview-digit {
width: 100%;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
font-size: 20px;
background: linear-gradient(145deg, #1a1a1a, #0a0a0a);
}
.counter-preview {
text-align: center;
margin-bottom: 20px;
padding: 20px;
background: rgba(0, 0, 0, 0.5);
border-radius: 10px;
border: 1px solid #00ff00;
}
.counter-preview h3 {
color: #00ff00;
margin-bottom: 10px;
}
.preview-digits {
display: flex;
justify-content: center;
gap: 5px;
font-family: 'OpenGostTypeB', monospace;
font-size: 2em;
color: #00ff00;
text-shadow: 0 0 10px #00ff00;
}
.preview-digit {
width: 40px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.8);
border: 1px solid #00ff00;
border-radius: 5px;
}
</style>
</head>
<body>
<div class="container">
<h1>Mechanical Counter Controller</h1>
<div id="status" class="status disconnected">Disconnected</div>
<div class="font-size-controls">
<h3>Global Font Size</h3>
<div class="font-size-group">
<div class="font-size-label">Font Size:</div>
<input type="range" class="font-size-slider" id="fontSizeSlider" min="12" max="120" value="48">
<span class="font-size-value" id="fontSizeValue">48px</span>
<div class="font-size-preview">
<span id="fontSizePreview" style="font-size: 48px;">123</span>
</div>
</div>
<div style="text-align: center;">
<button class="btn btn-primary" onclick="updateFontSize()">Update Font Size</button>
<button class="btn btn-secondary" onclick="resetFontSize()">Reset Font Size</button>
</div>
</div>
<div class="animation-speed-controls">
<h3>Global Animation Speed Control</h3>
<div class="animation-speed-group">
<div class="animation-speed-label">Speed:</div>
<input type="range" class="animation-speed-slider" id="globalAnimationSpeed" min="0.1" max="2.0" value="0.5" step="0.1">
<span class="animation-speed-value" id="globalAnimationSpeedValue">0.5s</span>
<div class="animation-speed-preview">
<div class="animation-speed-preview-box">
<div class="animation-speed-preview-strip" id="speedPreviewStrip">
<div class="animation-speed-preview-digit">0</div>
<div class="animation-speed-preview-digit">1</div>
<div class="animation-speed-preview-digit">2</div>
<div class="animation-speed-preview-digit">3</div>
<div class="animation-speed-preview-digit">4</div>
<div class="animation-speed-preview-digit">5</div>
<div class="animation-speed-preview-digit">6</div>
<div class="animation-speed-preview-digit">7</div>
<div class="animation-speed-preview-digit">8</div>
<div class="animation-speed-preview-digit">9</div>
</div>
</div>
</div>
</div>
<div style="text-align: center;">
<button class="btn btn-primary" onclick="updateGlobalAnimationSpeed()">Update All Wheels</button>
<button class="btn btn-secondary" onclick="resetGlobalAnimationSpeed()">Reset to Default</button>
<button class="btn btn-warning" onclick="testAnimationSpeed()">Test Animation</button>
</div>
</div>
<div class="brightness-controls">
<h3>Global Brightness Control</h3>
<div class="brightness-group">
<div class="brightness-label">Brightness:</div>
<input type="range" class="brightness-slider" id="globalBrightness" min="0" max="100" value="100" step="5">
<span class="brightness-value" id="globalBrightnessValue">100%</span>
<div class="brightness-preview">
<div class="brightness-preview-box" id="brightnessPreviewBox">
<div class="brightness-preview-digit" id="brightnessPreviewDigit">8</div>
</div>
</div>
</div>
<div style="text-align: center;">
<button class="btn btn-primary" onclick="updateGlobalBrightness()">Update All Wheels</button>
<button class="btn btn-secondary" onclick="resetGlobalBrightness()">Reset to Default</button>
</div>
</div>
<div class="wheel-size-controls">
<h3>Digit Wheel Size</h3>
<div class="wheel-size-group">
<div class="wheel-size-label">Width:</div>
<input type="range" class="wheel-size-slider" id="wheelWidthSlider" min="40" max="200" value="80">
<span class="wheel-size-value" id="wheelWidthValue">80px</span>
<div class="wheel-size-label">Height:</div>
<input type="range" class="wheel-size-slider" id="wheelHeightSlider" min="60" max="300" value="120">
<span class="wheel-size-value" id="wheelHeightValue">120px</span>
</div>
<div class="wheel-size-group">
<div class="wheel-size-preview">
<div class="wheel-size-preview-box" id="wheelSizePreview" style="width: 80px; height: 120px;">
<div style="color: #ffffff; font-size: 24px; line-height: 120px; text-align: center;">8</div>
</div>
</div>
</div>
<div style="text-align: center;">
<button class="btn btn-primary" onclick="updateWheelSize()">Update Wheel Size</button>
<button class="btn btn-secondary" onclick="resetWheelSize()">Reset Wheel Size</button>
</div>
</div>
<div class="position-controls">
<h3>Digit Positions</h3>
<div class="digit-position-group">
<div class="digit-position-label">Digit 1:</div>
<div class="position-slider-group">
<span>X:</span>
<input type="range" class="position-slider" id="pos1x" min="0" max="800" value="50">
<span class="position-value" id="pos1xval">50</span>
<span>Y:</span>
<input type="range" class="position-slider" id="pos1y" min="0" max="600" value="200">
<span class="position-value" id="pos1yval">200</span>
</div>
</div>
<div class="digit-position-group">
<div class="digit-position-label">Digit 2:</div>
<div class="position-slider-group">
<span>X:</span>
<input type="range" class="position-slider" id="pos2x" min="0" max="800" value="150">
<span class="position-value" id="pos2xval">150</span>
<span>Y:</span>
<input type="range" class="position-slider" id="pos2y" min="0" max="600" value="200">
<span class="position-value" id="pos2yval">200</span>
</div>
</div>
<div class="digit-position-group">
<div class="digit-position-label">Digit 3:</div>
<div class="position-slider-group">
<span>X:</span>
<input type="range" class="position-slider" id="pos3x" min="0" max="800" value="250">
<span class="position-value" id="pos3xval">250</span>
<span>Y:</span>
<input type="range" class="position-slider" id="pos3y" min="0" max="600" value="200">
<span class="position-value" id="pos3yval">200</span>
</div>
</div>
<div class="digit-position-group">
<div class="digit-position-label">Digit 4:</div>
<div class="position-slider-group">
<span>X:</span>
<input type="range" class="position-slider" id="pos4x" min="0" max="800" value="350">
<span class="position-value" id="pos4xval">350</span>
<span>Y:</span>
<input type="range" class="position-slider" id="pos4y" min="0" max="600" value="200">
<span class="position-value" id="pos4yval">200</span>
</div>
</div>
<div class="digit-position-group">
<div class="digit-position-label">Digit 5:</div>
<div class="position-slider-group">
<span>X:</span>
<input type="range" class="position-slider" id="pos5x" min="0" max="800" value="450">
<span class="position-value" id="pos5xval">450</span>
<span>Y:</span>
<input type="range" class="position-slider" id="pos5y" min="0" max="600" value="200">
<span class="position-value" id="pos5yval">200</span>
</div>
</div>
<div class="digit-position-group">
<div class="digit-position-label">Digit 6:</div>
<div class="position-slider-group">
<span>X:</span>
<input type="range" class="position-slider" id="pos6x" min="0" max="800" value="550">
<span class="position-value" id="pos6xval">550</span>
<span>Y:</span>
<input type="range" class="position-slider" id="pos6y" min="0" max="600" value="200">
<span class="position-value" id="pos6yval">200</span>
</div>
</div>
<div style="text-align: center; margin-top: 15px;">
<div style="color: #00ff00; margin-bottom: 10px;">
Viewport: <span id="viewportInfo">800 x 600</span>
</div>
<button class="btn btn-primary" onclick="updatePositions()">Update Positions</button>
<button class="btn btn-secondary" onclick="resetPositions()">Reset Positions</button>
<button class="btn btn-warning" onclick="requestViewportUpdate()" style="margin-left: 10px;">Request Viewport Update</button>
</div>
</div>
<div class="counter-preview">
<h3>Current Counter Value</h3>
<div class="preview-digits" id="previewDigits">
<div class="preview-digit">0</div>
<div class="preview-digit">0</div>
<div class="preview-digit">0</div>
<div class="preview-digit">0</div>
<div class="preview-digit">0</div>
<div class="preview-digit">0</div>
</div>
</div>
<div class="counter-inputs">
<div class="digit-input-group">
<label class="digit-label">Digit 1</label>
<input type="number" class="digit-input" id="digit1" min="0" max="9" value="0">
<div class="digit-buttons">
<button class="digit-btn digit-btn-up" onclick="incrementDigit(0)"></button>
<button class="digit-btn digit-btn-down" onclick="decrementDigit(0)"></button>
</div>
</div>
<div class="digit-input-group">
<label class="digit-label">Digit 2</label>
<input type="number" class="digit-input" id="digit2" min="0" max="9" value="0">
<div class="digit-buttons">
<button class="digit-btn digit-btn-up" onclick="incrementDigit(1)"></button>
<button class="digit-btn digit-btn-down" onclick="decrementDigit(1)"></button>
</div>
</div>
<div class="digit-input-group">
<label class="digit-label">Digit 3</label>
<input type="number" class="digit-input" id="digit3" min="0" max="9" value="0">
<div class="digit-buttons">
<button class="digit-btn digit-btn-up" onclick="incrementDigit(2)"></button>
<button class="digit-btn digit-btn-down" onclick="decrementDigit(2)"></button>
</div>
</div>
<div class="digit-input-group">
<label class="digit-label">Digit 4</label>
<input type="number" class="digit-input" id="digit4" min="0" max="9" value="0">
<div class="digit-buttons">
<button class="digit-btn digit-btn-up" onclick="incrementDigit(3)"></button>
<button class="digit-btn digit-btn-down" onclick="decrementDigit(3)"></button>
</div>
</div>
<div class="digit-input-group">
<label class="digit-label">Digit 5</label>
<input type="number" class="digit-input" id="digit5" min="0" max="9" value="0">
<div class="digit-buttons">
<button class="digit-btn digit-btn-up" onclick="incrementDigit(4)"></button>
<button class="digit-btn digit-btn-down" onclick="decrementDigit(4)"></button>
</div>
</div>
<div class="digit-input-group">
<label class="digit-label">Digit 6</label>
<input type="number" class="digit-input" id="digit6" min="0" max="9" value="0">
<div class="digit-buttons">
<button class="digit-btn digit-btn-up" onclick="incrementDigit(5)"></button>
<button class="digit-btn digit-btn-down" onclick="decrementDigit(5)"></button>
</div>
</div>
</div>
<div class="controls">
<button class="btn btn-primary" onclick="updateCounter()">Update Counter</button>
<button class="btn btn-secondary" onclick="resetCounter()">Reset to Zero</button>
<button class="btn btn-warning" onclick="randomCounter()">Random Value</button>
</div>
<div class="quick-actions">
<h3>Quick Actions</h3>
<div class="quick-buttons">
<button class="quick-btn" onclick="setCounter([1,2,3,4,5,6])">123456</button>
<button class="quick-btn" onclick="setCounter([9,9,9,9,9,9])">999999</button>
<button class="quick-btn" onclick="setCounter([0,0,0,0,0,0])">000000</button>
<button class="quick-btn" onclick="setCounter([1,0,0,0,0,0])">100000</button>
<button class="quick-btn" onclick="setCounter([0,0,0,0,0,1])">000001</button>
</div>
</div>
<div class="sent-events">
<h3>Sent Updates</h3>
<div id="sentEvents"></div>
</div>
<a href="/" class="nav-link">← Back to Home</a>
</div>
<script>
// Connect to WebSocket server
const socket = io();
const statusDiv = document.getElementById('status');
const sentEventsDiv = document.getElementById('sentEvents');
const digitInputs = [
document.getElementById('digit1'),
document.getElementById('digit2'),
document.getElementById('digit3'),
document.getElementById('digit4'),
document.getElementById('digit5'),
document.getElementById('digit6')
];
const previewDigits = document.querySelectorAll('.preview-digit');
// Current counter values
let currentValues = [0, 0, 0, 0, 0, 0];
// Current position values
let currentPositions = [
{x: 50, y: 200},
{x: 150, y: 200},
{x: 250, y: 200},
{x: 350, y: 200},
{x: 450, y: 200},
{x: 550, y: 200}
];
// Debounce timer for position updates
let positionUpdateTimer = null;
// Viewport dimensions (default values)
let viewportWidth = 800;
let viewportHeight = 600;
// Font size (default value)
let currentFontSize = 48;
// Wheel size (default values)
let currentWheelWidth = 80;
let currentWheelHeight = 120;
// Animation speed (default value)
let currentAnimationSpeed = 0.5;
// Brightness (default value)
let currentBrightness = 100;
// Connection status
socket.on('connect', function() {
statusDiv.textContent = 'Connected';
statusDiv.className = 'status connected';
console.log('Connected to server');
});
socket.on('disconnect', function() {
statusDiv.textContent = 'Disconnected';
statusDiv.className = 'status disconnected';
console.log('Disconnected from server');
});
// Handle viewport updates from clients
socket.on('viewport_update', function(data) {
console.log('Received viewport update:', data);
console.log('Current viewport before update:', viewportWidth, 'x', viewportHeight);
updateViewportRanges(data.width, data.height);
console.log('Updated viewport to:', viewportWidth, 'x', viewportHeight);
});
// Add event listeners to digit inputs
digitInputs.forEach((input, index) => {
input.addEventListener('input', function() {
const value = parseInt(this.value) || 0;
currentValues[index] = value;
updatePreview();
});
input.addEventListener('change', function() {
// Ensure value is between 0-9
let value = parseInt(this.value) || 0;
value = Math.max(0, Math.min(9, value));
this.value = value;
currentValues[index] = value;
updatePreview();
});
});
// Add event listeners to position sliders
for (let i = 1; i <= 6; i++) {
const xSlider = document.getElementById(`pos${i}x`);
const ySlider = document.getElementById(`pos${i}y`);
const xValue = document.getElementById(`pos${i}xval`);
const yValue = document.getElementById(`pos${i}yval`);
xSlider.addEventListener('input', function() {
xValue.textContent = this.value;
currentPositions[i-1].x = parseInt(this.value);
// Send live update with debounce
debouncedPositionUpdate();
});
ySlider.addEventListener('input', function() {
yValue.textContent = this.value;
currentPositions[i-1].y = parseInt(this.value);
// Send live update with debounce
debouncedPositionUpdate();
});
}
// Add event listener to font size slider
const fontSizeSlider = document.getElementById('fontSizeSlider');
const fontSizeValue = document.getElementById('fontSizeValue');
const fontSizePreview = document.getElementById('fontSizePreview');
fontSizeSlider.addEventListener('input', function() {
const newSize = parseInt(this.value);
currentFontSize = newSize;
fontSizeValue.textContent = newSize + 'px';
fontSizePreview.style.fontSize = newSize + 'px';
});
// Add event listeners to wheel size sliders
const wheelWidthSlider = document.getElementById('wheelWidthSlider');
const wheelHeightSlider = document.getElementById('wheelHeightSlider');
const wheelWidthValue = document.getElementById('wheelWidthValue');
const wheelHeightValue = document.getElementById('wheelHeightValue');
const wheelSizePreview = document.getElementById('wheelSizePreview');
wheelWidthSlider.addEventListener('input', function() {
const newWidth = parseInt(this.value);
currentWheelWidth = newWidth;
wheelWidthValue.textContent = newWidth + 'px';
wheelSizePreview.style.width = newWidth + 'px';
});
wheelHeightSlider.addEventListener('input', function() {
const newHeight = parseInt(this.value);
currentWheelHeight = newHeight;
wheelHeightValue.textContent = newHeight + 'px';
wheelSizePreview.style.height = newHeight + 'px';
wheelSizePreview.querySelector('div').style.lineHeight = newHeight + 'px';
});
// Add event listener to animation speed slider
const animationSpeedSlider = document.getElementById('globalAnimationSpeed');
const animationSpeedValue = document.getElementById('globalAnimationSpeedValue');
const speedPreviewStrip = document.getElementById('speedPreviewStrip');
animationSpeedSlider.addEventListener('input', function() {
const newSpeed = parseFloat(this.value);
currentAnimationSpeed = newSpeed;
animationSpeedValue.textContent = newSpeed + 's';
speedPreviewStrip.style.transition = `transform ${newSpeed}s ease`;
});
// Add event listener to brightness slider
const brightnessSlider = document.getElementById('globalBrightness');
const brightnessValue = document.getElementById('globalBrightnessValue');
const brightnessPreviewBox = document.getElementById('brightnessPreviewBox');
brightnessSlider.addEventListener('input', function() {
const newBrightness = parseInt(this.value);
currentBrightness = newBrightness;
brightnessValue.textContent = newBrightness + '%';
// Update preview with brightness filter
const brightnessFilter = newBrightness / 100;
brightnessPreviewBox.style.filter = `brightness(${brightnessFilter})`;
});
function updateCounter() {
const counterData = {
type: 'counter_update',
values: [...currentValues],
timestamp: new Date().toISOString()
};
socket.emit('counter_update', counterData);
addSentEvent(counterData);
}
function resetCounter() {
setCounter([0, 0, 0, 0, 0, 0]);
}
function randomCounter() {
const randomValues = Array.from({length: 6}, () => Math.floor(Math.random() * 10));
setCounter(randomValues);
}
function setCounter(values) {
values.forEach((value, index) => {
digitInputs[index].value = value;
currentValues[index] = value;
});
updatePreview();
updateCounter();
}
function updatePreview() {
currentValues.forEach((value, index) => {
previewDigits[index].textContent = value;
});
}
function updatePositions() {
const positionData = {
type: 'position_update',
positions: [...currentPositions],
timestamp: new Date().toISOString()
};
socket.emit('position_update', positionData);
addSentEvent(positionData);
}
function resetPositions() {
const defaultPositions = [
{x: 50, y: 200},
{x: 150, y: 200},
{x: 250, y: 200},
{x: 350, y: 200},
{x: 450, y: 200},
{x: 550, y: 200}
];
// Update sliders and values
for (let i = 1; i <= 6; i++) {
const xSlider = document.getElementById(`pos${i}x`);
const ySlider = document.getElementById(`pos${i}y`);
const xValue = document.getElementById(`pos${i}xval`);
const yValue = document.getElementById(`pos${i}yval`);
xSlider.value = defaultPositions[i-1].x;
ySlider.value = defaultPositions[i-1].y;
xValue.textContent = defaultPositions[i-1].x;
yValue.textContent = defaultPositions[i-1].y;
}
currentPositions = [...defaultPositions];
updatePositions();
}
function debouncedPositionUpdate() {
if (positionUpdateTimer) {
clearTimeout(positionUpdateTimer);
}
positionUpdateTimer = setTimeout(() => {
sendLivePositionUpdate();
}, 50); // 50ms debounce
}
function sendLivePositionUpdate() {
const positionData = {
type: 'position_update',
positions: [...currentPositions],
timestamp: new Date().toISOString()
};
console.log('Sending position update:', positionData);
socket.emit('position_update', positionData);
}
function updateViewportRanges(width, height) {
console.log('updateViewportRanges called with:', width, 'x', height);
viewportWidth = width;
viewportHeight = height;
// Update viewport info display
const viewportInfoElement = document.getElementById('viewportInfo');
if (viewportInfoElement) {
viewportInfoElement.textContent = `${width} x ${height}`;
console.log('Updated viewport info display to:', width, 'x', height);
} else {
console.error('Viewport info element not found!');
}
// Update all slider ranges
for (let i = 1; i <= 6; i++) {
const xSlider = document.getElementById(`pos${i}x`);
const ySlider = document.getElementById(`pos${i}y`);
if (xSlider && ySlider) {
xSlider.max = width;
ySlider.max = height;
// Ensure current values don't exceed new ranges
const currentX = parseInt(xSlider.value);
const currentY = parseInt(ySlider.value);
if (currentX > width) {
xSlider.value = width;
currentPositions[i-1].x = width;
document.getElementById(`pos${i}xval`).textContent = width;
}
if (currentY > height) {
ySlider.value = height;
currentPositions[i-1].y = height;
document.getElementById(`pos${i}yval`).textContent = height;
}
}
}
console.log(`Updated viewport ranges to ${width}x${height}`);
}
function updateFontSize() {
const fontSizeData = {
type: 'font_size_update',
fontSize: currentFontSize,
timestamp: new Date().toISOString()
};
console.log('Sending font size update:', fontSizeData);
socket.emit('font_size_update', fontSizeData);
addSentEvent(fontSizeData);
}
function updateWheelSize() {
const wheelSizeData = {
type: 'wheel_size_update',
width: currentWheelWidth,
height: currentWheelHeight,
timestamp: new Date().toISOString()
};
console.log('Sending wheel size update:', wheelSizeData);
socket.emit('wheel_size_update', wheelSizeData);
addSentEvent(wheelSizeData);
}
function resetWheelSize() {
const defaultWidth = 80;
const defaultHeight = 120;
wheelWidthSlider.value = defaultWidth;
wheelHeightSlider.value = defaultHeight;
currentWheelWidth = defaultWidth;
currentWheelHeight = defaultHeight;
wheelWidthValue.textContent = defaultWidth + 'px';
wheelHeightValue.textContent = defaultHeight + 'px';
wheelSizePreview.style.width = defaultWidth + 'px';
wheelSizePreview.style.height = defaultHeight + 'px';
wheelSizePreview.querySelector('div').style.lineHeight = defaultHeight + 'px';
updateWheelSize();
}
function resetFontSize() {
const defaultSize = 48;
fontSizeSlider.value = defaultSize;
currentFontSize = defaultSize;
fontSizeValue.textContent = defaultSize + 'px';
fontSizePreview.style.fontSize = defaultSize + 'px';
updateFontSize();
}
function updateGlobalAnimationSpeed() {
const animationSpeedData = {
type: 'animation_speed_update',
speed: currentAnimationSpeed,
timestamp: new Date().toISOString()
};
console.log('Sending animation speed update:', animationSpeedData);
socket.emit('animation_speed_update', animationSpeedData);
addSentEvent(animationSpeedData);
}
function resetGlobalAnimationSpeed() {
const defaultSpeed = 0.5;
animationSpeedSlider.value = defaultSpeed;
currentAnimationSpeed = defaultSpeed;
animationSpeedValue.textContent = defaultSpeed + 's';
speedPreviewStrip.style.transition = `transform ${defaultSpeed}s ease`;
updateGlobalAnimationSpeed();
}
function testAnimationSpeed() {
// Animate the preview strip to show the current speed
const strip = document.getElementById('speedPreviewStrip');
const currentTransform = strip.style.transform;
// Reset to top
strip.style.transform = 'translateY(0px)';
// Animate to show digit 5
setTimeout(() => {
strip.style.transform = 'translateY(-300px)';
}, 100);
// Reset back to top after animation
setTimeout(() => {
strip.style.transform = currentTransform;
}, (currentAnimationSpeed * 1000) + 200);
}
function updateGlobalBrightness() {
const brightnessData = {
type: 'brightness_update',
brightness: currentBrightness,
timestamp: new Date().toISOString()
};
console.log('Sending brightness update:', brightnessData);
socket.emit('brightness_update', brightnessData);
addSentEvent(brightnessData);
}
function resetGlobalBrightness() {
const defaultBrightness = 100;
brightnessSlider.value = defaultBrightness;
currentBrightness = defaultBrightness;
brightnessValue.textContent = defaultBrightness + '%';
brightnessPreviewBox.style.filter = 'brightness(1)';
updateGlobalBrightness();
}
function incrementDigit(index) {
let currentValue = currentValues[index];
currentValue = (currentValue + 1) % 10; // Wrap around from 9 to 0
currentValues[index] = currentValue;
// Update input field
digitInputs[index].value = currentValue;
// Update preview
updatePreview();
// Send instant update
updateCounter();
}
function decrementDigit(index) {
let currentValue = currentValues[index];
currentValue = (currentValue - 1 + 10) % 10; // Wrap around from 0 to 9
currentValues[index] = currentValue;
// Update input field
digitInputs[index].value = currentValue;
// Update preview
updatePreview();
// Send instant update
updateCounter();
}
function requestViewportUpdate() {
console.log('Requesting viewport update from clients...');
socket.emit('request_viewport', { timestamp: new Date().toISOString() });
}
function addSentEvent(data) {
const eventDiv = document.createElement('div');
eventDiv.className = 'sent-event';
const timeDiv = document.createElement('div');
timeDiv.className = 'sent-event-time';
timeDiv.textContent = new Date().toLocaleTimeString();
const dataDiv = document.createElement('div');
dataDiv.className = 'sent-event-data';
dataDiv.textContent = `Counter: ${data.values.join('')}`;
eventDiv.appendChild(timeDiv);
eventDiv.appendChild(dataDiv);
sentEventsDiv.appendChild(eventDiv);
// Auto-scroll to bottom
sentEventsDiv.scrollTop = sentEventsDiv.scrollHeight;
}
// Initialize preview
updatePreview();
</script>
</body>
</html>