|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
|
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Reachy Mini Simple Control Panel</title> |
|
|
<style> |
|
|
* { |
|
|
margin: 0; |
|
|
padding: 0; |
|
|
box-sizing: border-box; |
|
|
} |
|
|
|
|
|
body { |
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; |
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
|
min-height: 100vh; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
padding: 20px; |
|
|
} |
|
|
|
|
|
.container { |
|
|
background: white; |
|
|
border-radius: 20px; |
|
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); |
|
|
max-width: 600px; |
|
|
width: 100%; |
|
|
padding: 40px; |
|
|
} |
|
|
|
|
|
h1 { |
|
|
color: #333; |
|
|
margin-bottom: 10px; |
|
|
font-size: 28px; |
|
|
} |
|
|
|
|
|
.subtitle { |
|
|
color: #666; |
|
|
margin-bottom: 30px; |
|
|
font-size: 14px; |
|
|
} |
|
|
|
|
|
.section { |
|
|
margin-bottom: 30px; |
|
|
} |
|
|
|
|
|
.section h2 { |
|
|
color: #555; |
|
|
font-size: 16px; |
|
|
margin-bottom: 15px; |
|
|
text-transform: uppercase; |
|
|
letter-spacing: 1px; |
|
|
} |
|
|
|
|
|
.status { |
|
|
padding: 12px; |
|
|
border-radius: 8px; |
|
|
background: #f5f5f5; |
|
|
margin-bottom: 10px; |
|
|
font-size: 14px; |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
align-items: center; |
|
|
} |
|
|
|
|
|
.status.connected { |
|
|
background: #d4edda; |
|
|
color: #155724; |
|
|
} |
|
|
|
|
|
.status.disconnected { |
|
|
background: #f8d7da; |
|
|
color: #721c24; |
|
|
} |
|
|
|
|
|
.status-dot { |
|
|
width: 10px; |
|
|
height: 10px; |
|
|
border-radius: 50%; |
|
|
margin-right: 8px; |
|
|
display: inline-block; |
|
|
} |
|
|
|
|
|
.status-dot.green { |
|
|
background: #28a745; |
|
|
box-shadow: 0 0 10px #28a745; |
|
|
} |
|
|
|
|
|
.status-dot.red { |
|
|
background: #dc3545; |
|
|
} |
|
|
|
|
|
button { |
|
|
background: #667eea; |
|
|
color: white; |
|
|
border: none; |
|
|
padding: 12px 24px; |
|
|
border-radius: 8px; |
|
|
font-size: 14px; |
|
|
font-weight: 600; |
|
|
cursor: pointer; |
|
|
transition: all 0.3s; |
|
|
margin-right: 10px; |
|
|
margin-bottom: 10px; |
|
|
} |
|
|
|
|
|
button:hover { |
|
|
background: #5568d3; |
|
|
transform: translateY(-2px); |
|
|
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); |
|
|
} |
|
|
|
|
|
button:active { |
|
|
transform: translateY(0); |
|
|
} |
|
|
|
|
|
button:disabled { |
|
|
background: #ccc; |
|
|
cursor: not-allowed; |
|
|
transform: none; |
|
|
} |
|
|
|
|
|
button.secondary { |
|
|
background: #6c757d; |
|
|
} |
|
|
|
|
|
button.secondary:hover { |
|
|
background: #5a6268; |
|
|
} |
|
|
|
|
|
button.danger { |
|
|
background: #dc3545; |
|
|
} |
|
|
|
|
|
button.danger:hover { |
|
|
background: #c82333; |
|
|
} |
|
|
|
|
|
.slider-group { |
|
|
margin-bottom: 20px; |
|
|
} |
|
|
|
|
|
label { |
|
|
display: block; |
|
|
color: #555; |
|
|
font-size: 13px; |
|
|
margin-bottom: 8px; |
|
|
font-weight: 500; |
|
|
} |
|
|
|
|
|
.slider-container { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 10px; |
|
|
} |
|
|
|
|
|
input[type="range"] { |
|
|
flex: 1; |
|
|
height: 6px; |
|
|
border-radius: 3px; |
|
|
background: #ddd; |
|
|
outline: none; |
|
|
-webkit-appearance: none; |
|
|
} |
|
|
|
|
|
input[type="range"]::-webkit-slider-thumb { |
|
|
-webkit-appearance: none; |
|
|
appearance: none; |
|
|
width: 18px; |
|
|
height: 18px; |
|
|
border-radius: 50%; |
|
|
background: #667eea; |
|
|
cursor: pointer; |
|
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); |
|
|
} |
|
|
|
|
|
input[type="range"]::-moz-range-thumb { |
|
|
width: 18px; |
|
|
height: 18px; |
|
|
border-radius: 50%; |
|
|
background: #667eea; |
|
|
cursor: pointer; |
|
|
border: none; |
|
|
} |
|
|
|
|
|
.slider-value { |
|
|
min-width: 60px; |
|
|
text-align: right; |
|
|
color: #666; |
|
|
font-size: 13px; |
|
|
font-family: 'Courier New', monospace; |
|
|
} |
|
|
|
|
|
.button-row { |
|
|
display: flex; |
|
|
flex-wrap: wrap; |
|
|
gap: 10px; |
|
|
} |
|
|
|
|
|
@media (max-width: 600px) { |
|
|
.container { |
|
|
padding: 20px; |
|
|
} |
|
|
|
|
|
h1 { |
|
|
font-size: 24px; |
|
|
} |
|
|
|
|
|
button { |
|
|
width: 100%; |
|
|
margin-right: 0; |
|
|
} |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
|
|
|
<body> |
|
|
<div class="container"> |
|
|
<h1>🤖 Reachy Mini Control Panel</h1> |
|
|
<p class="subtitle">WebSocket Real-time Control</p> |
|
|
|
|
|
|
|
|
<div class="section"> |
|
|
<div id="connectionStatus" class="status disconnected"> |
|
|
<span><span class="status-dot red"></span>Disconnected</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="section"> |
|
|
<h2>Head Pose Control</h2> |
|
|
|
|
|
<div class="slider-group"> |
|
|
<label>X [m]</label> |
|
|
<div class="slider-container"> |
|
|
<input type="range" id="headX" min="-0.2" max="0.2" step="0.001" value="0" disabled> |
|
|
<span class="slider-value" id="headXValue">0.000</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="slider-group"> |
|
|
<label>Y [m]</label> |
|
|
<div class="slider-container"> |
|
|
<input type="range" id="headY" min="-0.2" max="0.2" step="0.001" value="0" disabled> |
|
|
<span class="slider-value" id="headYValue">0.000</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="slider-group"> |
|
|
<label>Z [m]</label> |
|
|
<div class="slider-container"> |
|
|
<input type="range" id="headZ" min="-0.2" max="0.2" step="0.001" value="0" disabled> |
|
|
<span class="slider-value" id="headZValue">0.000</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="slider-group"> |
|
|
<label>Roll [rad]</label> |
|
|
<div class="slider-container"> |
|
|
<input type="range" id="headRoll" min="-3.2" max="3.2" step="0.01" value="0" disabled> |
|
|
<span class="slider-value" id="headRollValue">0.00</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="slider-group"> |
|
|
<label>Pitch [rad]</label> |
|
|
<div class="slider-container"> |
|
|
<input type="range" id="headPitch" min="-3.2" max="3.2" step="0.01" value="0" disabled> |
|
|
<span class="slider-value" id="headPitchValue">0.00</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="slider-group"> |
|
|
<label>Yaw [rad]</label> |
|
|
<div class="slider-container"> |
|
|
<input type="range" id="headYaw" min="-3.2" max="3.2" step="0.01" value="0" disabled> |
|
|
<span class="slider-value" id="headYawValue">0.00</span> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="section"> |
|
|
<h2>Body & Antennas</h2> |
|
|
|
|
|
<div class="slider-group"> |
|
|
<label>Body Yaw [rad]</label> |
|
|
<div class="slider-container"> |
|
|
<input type="range" id="bodyYaw" min="-3.2" max="3.2" step="0.01" value="0" disabled> |
|
|
<span class="slider-value" id="bodyYawValue">0.00</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="slider-group"> |
|
|
<label>Left Antenna [rad]</label> |
|
|
<div class="slider-container"> |
|
|
<input type="range" id="antennaLeft" min="-3.2" max="3.2" step="0.01" value="0" disabled> |
|
|
<span class="slider-value" id="antennaLeftValue">0.00</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="slider-group"> |
|
|
<label>Right Antenna [rad]</label> |
|
|
<div class="slider-container"> |
|
|
<input type="range" id="antennaRight" min="-3.2" max="3.2" step="0.01" value="0" disabled> |
|
|
<span class="slider-value" id="antennaRightValue">0.00</span> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script src="app.js"></script> |
|
|
</body> |
|
|
|
|
|
</html> |