| <!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> |