feat: 初始化 Go2 导航代码与配置

This commit is contained in:
2026-10-14 11:08:44 +08:00
parent 8e16ecceba
commit 9dab37e8c8
1258 changed files with 1936673 additions and 0 deletions

7
unitree_ui/unitree_ui-main/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
_frontend_1.11.4/
reverse_engineer/
INSTRUCTIONS.md
test/
tests/
node_modules/
dist/

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Konstantin Severov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,144 @@
# Unitree WebRTC UI
A browser-based control interface for **Unitree Go2**, **Unitree G1**, and **Unitree R1** robots, communicating over the same WebRTC connection the official mobile apps use. No jailbreak, no firmware modification, no phone app required.
Built with TypeScript, Three.js, and Vite.
![Status](https://img.shields.io/badge/status-stable-brightgreen)
![Browser](https://img.shields.io/badge/tested-Chrome-brightgreen)
![License](https://img.shields.io/badge/license-MIT-yellow.svg)
<p align="center">
<img src="images/webview_go2.png" width="90%" />
</p>
<p align="center">
<img src="images/connect_go2.png" width="32%" />
<img src="images/connect_g1.png" width="32%" />
<img src="images/connect_r1.png" width="32%" />
</p>
## Supported Robots
| Family | Firmware |
|--------|----------|
| **Go2** | 1.0.19 – 1.0.25, 1.1.1 – 1.1.15 *(latest)* |
| **G1** | 1.2.0 – 1.4.5, 1.5.1+ *(latest)* |
| **R1** | 1.4.2 *(latest)* |
R1 shares the humanoid control channel with G1, so it inherits the same modes,
arm gestures, status panels and e-stop. Its own FSM state ids differ — Run rides
on 811 (`AmpMotion22Dof`) rather than 801, and it adds the dance / martial-arts
set — all catalogued in [R1 Action List](docs/r1_action_list.md).
## Tour
<p align="center">
<img src="images/landing.png" width="32%" />
<img src="images/hub.png" width="32%" />
<img src="images/webview_g1.png" width="32%" />
</p>
<p align="center">
<img src="images/status_g1.png" width="32%" />
<img src="images/services_go2.png" width="32%" />
<img src="images/slam.png" width="32%" />
</p>
## Quick Start
```bash
git clone https://github.com/legion1581/unitree_ui.git
cd unitree_ui
npm install
npm run start # Vite + Python BLE server together
# or:
npm run start:no-ble # Vite only, no Bluetooth features
```
`npm run start` and `npm run start:no-ble` already bind Vite to all interfaces, so the terminal will print both a `Local:` (`http://localhost:5173`) and a `Network:` (`http://192.168.x.x:5173`) URL — open the Local one on this machine, the Network one from your phone or another device on the same WiFi. Use **Chrome** (recommended).
If you want Vite by itself (no BLE backend), `npm run dev` is localhost-only; `npm run dev:host` is the LAN-exposed equivalent.
The dev server includes hot module replacement, a built-in UDP multicast scanner (no separate process needed), and proxies for the robot API, Unitree cloud API, and BLE server.
For a production build:
```bash
npm run build && npm run preview
```
### Prerequisites
- Node.js ≥ 18, npm ≥ 9
- Chrome (Firefox is experimental, Safari untested)
- A Unitree Go2, G1, or R1 robot
## Features
- **Real-time 3D viewport** — robot model with live joint angles, lidar spinning animation, voxel point cloud (Go2 SLAM).
- **Camera + dual joystick control** — PIP video, on-screen joysticks, action carousel for sport commands and modes. On R1 the bar mirrors the official app (Damping / Zero Torque / Lock / Run plus the arm gestures) and adds the dance and Kung Fu / Jeet Kune Do states.
- **Audio** — two-way over the WebRTC connection: push-to-talk megaphone broadcast from your mic, click-to-toggle monitoring of the robot's microphone, and an APK-style **Audio Player** in Controls (record clips, upload MP3/WAV — auto-converted to the robot's WAV format, play with single / list / no-loop modes, rename, delete; live play-state sync).
- **Robot status** — battery, motors (temp / position / torque / lost packets), IMU, LiDAR, system info — family-aware fields for Go2 and for the G1 / R1 humanoids.
- **Error handling** — live decoding of firmware fault messages with snapshot + delta reconciliation; NavBar badge with active-count chip, click-anchored popover, transient toast on new faults, and a grouped full-screen list of every active error.
- **Service manager** — list MCF services, start / stop with protection handling.
- **Account manager** — Unitree cloud account: devices, firmware, tutorials, sharing, raw debug API console.
- **3D LiDAR Mapping (SLAM)** — Go2 only: build maps, localize, navigate, patrol, auto-dock and charge; local IndexedDB cache + zip import/export.
- **Bluetooth setup** — pair the robot over BLE (V1/V2 for legacy firmware, V3 for G1 ≥ 1.5.1 / Go2 ≥ 1.1.15) to configure WiFi without the phone app.
- **BLE remote relay** — pair a Unitree BLE remote, forward joystick + buttons to the robot over WebRTC.
- **Dark / light theme** — floating toggle, persisted per-browser; the 3D scene adapts on the fly.
- **Connection modes** — Local Network (STA-L), Access Point (AP), Remote (TURN through Unitree cloud).
- **Network scanner** — UDP multicast auto-discovery, including SN-targeted scan for V3-capable firmware (G1 ≥ 1.5.1, Go2 ≥ 1.1.15).
## Documentation
| Topic | What's covered |
|-------|---------------|
| [Connection](docs/connection.md) | Family selection, STA-L / AP / Remote modes, network scan, AES-128 key flow for V3-capable firmware (G1 ≥ 1.5.1, Go2 ≥ 1.1.15) |
| [Control View](docs/control.md) | Joysticks, action bar, modes, sport command IDs, BLE remote relay |
| [R1 Action List](docs/r1_action_list.md) | R1 FSM state table (fsm_id), SetFsmId api, dances / Kung Fu / Jeet Kune Do |
| [Robot Status](docs/status.md) | Battery / motor / IMU / system panels for every family |
| [Error Handling](docs/error-handling.md) | Fault wire protocol, Go2 + G1 source/code catalog, badge / popover / page UI |
| [Service Manager](docs/services.md) | MCF service list, protection flag, start/stop |
| [Account Manager](docs/account.md) | Cloud sign-in, devices, tutorials, debug console |
| [Bluetooth](docs/bluetooth.md) | Robot provisioning + remote pairing overview |
| [Bluetooth V1/V2 protocol](docs/bluetooth-v1-v2.md) | GATT layout for legacy firmware (Go2 < 1.1.15, G1 < 1.5.1) |
| [Bluetooth V3 protocol](docs/bluetooth-v3.md) | Magic-prefix + GCM key exchange (G1 ≥ 1.5.1, Go2 ≥ 1.1.15) |
| [BLE Remote Control](docs/remote-control.md) | Frame layout, axes/buttons mapping, WebRTC relay |
| [SLAM](docs/slam.md) | Mapping → Localization → Navigation flow, patrol, auto-dock |
| [LiDAR](docs/lidar.md) | Point cloud decoding pipeline |
## Project Structure
```
src/
api/ # Unitree cloud API client (account, devices, firmware, …)
connection/ # WebRTC, local/remote connectors, network scanner
crypto/ # AES-ECB, RSA, AES-GCM for auth and SDP exchange
protocol/ # Data channel handler, topics, sport commands
ui/
components/ # Action bar, PIP camera, status/services/account pages, nav bar
scene/ # Three.js scene, robot model, voxel map
proxy-plugin.ts # Vite plugin: robot proxy, scanner, cloud API, BLE API proxy
public/
icons/ # Action and mode SVG icons
sprites/ # UI sprites and backgrounds
models/ # Go2.glb 3D model (the humanoids use the camera view)
server/
ble_server.py # FastAPI BLE backend (scan, connect, WiFi config)
scanner.mjs # Standalone UDP multicast scanner (optional)
```
## Acknowledgements
Big thanks to the [RoboLegion](https://robolegion.com) community.
## Support
If you like this project, please consider buying me a coffee:
<a href="https://www.buymeacoffee.com/legion1581" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
## License
MIT

View File

@@ -0,0 +1,32 @@
# Account Manager
Talks to Unitree's cloud API directly — no phone app required. Sign in with your Unitree credentials (or paste an access token), and the same surface area the mobile app uses opens up: device list, firmware images, tutorials, sharing, raw API console.
<p align="center">
<img src="../images/hub.png" width="80%" />
</p>
## Sign-in
Two paths:
- **Email + password** — standard login, session is persisted in `localStorage` and refreshed automatically.
- **Access token** — paste an existing `accessToken`; useful if you've fetched one with the `unitree-fetch-aes-key` CLI from [unitree_webrtc_connect](https://github.com/legion1581/unitree_webrtc_connect) or extracted it from the phone app.
The **family slot** on the landing page determines which `AppName` header signs cloud requests — `Go2` accounts hit `global-robot-api.unitree.com` with `AppName: Go2`; G1 accounts use `AppName: B2`. Pick the one your account is registered under.
## Tabs
### Devices
Robots bound to your account: online status, alias, SN, model, firmware version. Per-device actions: bind/unbind, share, fetch firmware images, view detail. Logging in here also primes the AES-128 key cache for any V3-capable device (G1 ≥ 1.5.1, Go2 ≥ 1.1.15) by pulling `dev.key` from `device/bind/list`.
The device detail screen also exposes a **Refresh AES Key** panel (between Firmware Updates and Danger Zone). Paste the 344-char base64 blob from the BT page (`344B RSA` row) and the cloud will RSA-decrypt it via `device/bindExtData`, persist the derived 16-byte key as `dev.key`, and seed the local cache — useful when a device was bound before its firmware moved to V3, or after a key rotation. Family-agnostic: the call's `AppName` header is pinned to the device's series, so refreshing a G1 key while logged in as Go2 family (or vice versa) works.
### Info
App version with APK download links, grouped video tutorials (queried as `tutorial/list?appName=<family>&type=<model>`), changelog, and announcements.
### Account
Profile, avatar, password change, region, session management.
### Debug
Raw API console — 77 endpoints across 13 categories (Auth, Devices, Firmware, WebRTC, Wallet, etc.). Lets you fire any cloud request with custom params and inspect the JSON response. Useful for protocol reverse-engineering or one-off operations that don't have a dedicated UI yet.

View File

@@ -0,0 +1,324 @@
# Bluetooth Protocol V1 / V2 — Unitree Robots
This document describes the legacy BLE provisioning protocol used to scan, connect, and configure Unitree robots (WiFi setup, serial number, AP MAC).
Related documents:
- [bluetooth-v3.md](bluetooth-v3.md) — V3 extension (`VERSION` 0xF1, `GCM_KEY` 0xF2) introduced on G1 firmware 1.5.1 and back-ported to Go2 1.1.15.
- [remote-control.md](remote-control.md) — BLE remote (`Unitree-*`) protocol and the WebRTC relay that forwards remote inputs to the robot.
## Firmware Compatibility
| Robot | Firmware | Service |
|---|---|---|
| Unitree Go2 | `< 1.1.11` | V1 / V2 (FFE0 service) |
| Unitree Go2 | `1.1.11 – 1.1.14` | V1 / V2 (NUS service) |
| Unitree Go2 | `≥ 1.1.15` | V1 / V2 (NUS) + [V3 extension](bluetooth-v3.md) |
| Unitree G1 | `< 1.5.1` | V1 / V2 |
| Unitree G1 | `≥ 1.5.1` | V1 / V2 + [V3 extension](bluetooth-v3.md) |
V1 and V2 share the same wire format, encryption, and command set. The only difference is the GATT service UUID used to expose the characteristics — see [Service UUIDs](#service-uuids).
## Table of Contents
- [System Requirements](#system-requirements)
- [Scanning](#scanning)
- [Service UUIDs](#service-uuids)
- [Encryption](#encryption)
- [Packet Format](#packet-format)
- [Robot Connection Flow](#robot-connection-flow)
- [Commands](#commands)
- [WiFi Configuration](#wifi-configuration)
---
## System Requirements
### Linux Packages
| Package | Purpose | Install |
|---|---|---|
| `bluez` | BlueZ daemon, `bluetoothctl`, `gatttool`, `hciconfig` | `sudo apt install bluez` |
The `bluez` package provides both the BlueZ D-Bus daemon (`bluetoothd`) used by `bleak` for the robot, and `gatttool` used by `pygatt` for the remote control.
### Python Packages
All listed in `server/requirements.txt`:
```
fastapi>=0.110.0 # REST API
uvicorn>=0.27.0 # ASGI server
bleak>=0.22.0 # BlueZ D-Bus BLE client (used for robot)
pycryptodome>=3.20.0 # AES-128-CFB encryption
pygatt>=4.0.0 # gatttool wrapper (used for remote control)
```
Install with:
```bash
pip install -r server/requirements.txt
```
### User Permissions
On **Ubuntu 22.04** (and some other distros), the default D-Bus policy at `/etc/dbus-1/system.d/bluetooth.conf` already allows any local user to communicate with `org.bluez`, so no group membership is needed.
On **Debian, Fedora, Arch, Ubuntu with hardened D-Bus policies**, add your user to the `bluetooth` group:
```bash
sudo usermod -aG bluetooth $USER
# Log out and back in for group changes to take effect
```
### Required Services
BlueZ daemon must be running:
```bash
sudo systemctl enable --now bluetooth
systemctl is-active bluetooth # should print "active"
```
### Bluetooth Adapter
Check your HCI adapter(s):
```bash
hciconfig -a
```
If the adapter shows `DOWN` (common for USB dongles after plug-in), bring it up:
```bash
sudo hciconfig hci0 up # or hci1, etc.
```
The BLE server exposes `/adapters` and `/adapter` endpoints to switch between adapters at runtime.
### What's NOT Required
- **Running as root** — not needed
- **`setcap CAP_NET_RAW` on binaries** — `gatttool` and `hcitool` delegate privileged ops to `bluetoothd` which already runs as root
- **Disabling AppArmor/SELinux** — default policies allow everything needed
### Troubleshooting
| Error | Fix |
|---|---|
| `Operation not permitted` during scan | Ensure `bluetoothd` is running (`systemctl status bluetooth`) |
| `org.bluez.Error.NotReady` | Adapter is DOWN — `sudo hciconfig hciX up` |
| `org.bluez.Error.NotAvailable: br-connection-profile-unavailable` (remote) | Normal for dual-mode remote on BlueZ 5.64; `pygatt` handles this automatically |
| `Could not connect to the system bus` | User not in `bluetooth` group on a distro that requires it |
| Adapter not found | USB dongle not recognized — check `dmesg | tail` for driver errors |
---
## Scanning
Robots and remotes are discovered via BLE advertisement.
### Device Name Prefixes
| Prefix | Device |
|--------|--------|
| `Go2_` | Unitree Go2 robot |
| `G1_` | Unitree G1 robot |
| `Unitree-` | BLE Remote Control (e.g. `Unitree-32KC0D`) — see [remote-control.md](remote-control.md) |
### Protocol Detection
The protocol variant is determined by which service UUID appears in the BLE advertisement:
| Service UUID | Variant | Firmware |
|---|---|---|
| `0000ffe0-0000-1000-8000-00805f9b34fb` | V1 (FFE0) | Go2 `< 1.1.11`, all G1 |
| `6e400001-b5a3-f393-e0a9-e50e24dcca9e` | V2 (NUS) | Go2 `≥ 1.1.11` |
---
## Service UUIDs
### V1 (FFE0)
| UUID | Role |
|---|---|
| `0000ffe0-0000-1000-8000-00805f9b34fb` | Service |
| `0000ffe1-0000-1000-8000-00805f9b34fb` | Notify (robot -> client) |
| `0000ffe2-0000-1000-8000-00805f9b34fb` | Write (client -> robot) |
### V2 (Nordic UART Service)
| UUID | Role |
|---|---|
| `6e400001-b5a3-f393-e0a9-e50e24dcca9e` | Service |
| `6e400002-b5a3-f393-e0a9-e50e24dcca9e` | Write / TX (client -> robot) |
| `6e400003-b5a3-f393-e0a9-e50e24dcca9e` | Notify / RX (robot -> client) |
Both variants use the same encryption, packet format, and command set — only the UUIDs differ.
---
## Encryption
All robot BLE packets are encrypted with AES-128-CFB before transmission and decrypted on receive.
| Parameter | Value |
|---|---|
| Algorithm | AES-128-CFB |
| Segment Size | 128 bits |
| Key | `df98b715d5c6ed2b25817b6f2554124a` |
| IV | `2841ae97419c2973296a0d4bdfe19a4f` |
```python
from Crypto.Cipher import AES
KEY = bytes.fromhex("df98b715d5c6ed2b25817b6f2554124a")
IV = bytes.fromhex("2841ae97419c2973296a0d4bdfe19a4f")
def encrypt(data: bytes) -> bytes:
return AES.new(KEY, AES.MODE_CFB, iv=IV, segment_size=128).encrypt(data)
def decrypt(data: bytes) -> bytes:
return AES.new(KEY, AES.MODE_CFB, iv=IV, segment_size=128).decrypt(data)
```
> **Note:** The V3 extension (G1 ≥ 1.5.1, Go2 ≥ 1.1.15) sends a small set of additional commands **unencrypted** with a different magic prefix. See [bluetooth-v3.md](bluetooth-v3.md). On a V1/V2 connection, frames whose first byte is `0x00` (rather than the encrypted-frame distribution) belong to V3 and must be routed to the V3 handler before AES decryption is attempted.
---
## Packet Format
### Client -> Robot (Request)
After building the plaintext packet, it is AES-encrypted before writing to the GATT characteristic.
#### Simple Packet
```
[0x52] [length] [instruction] [data...] [checksum]
```
| Field | Size | Description |
|---|---|---|
| Header | 1 | Always `0x52` |
| Length | 1 | `len(data) + 4` (counts header, length, instruction, checksum) |
| Instruction | 1 | Command ID |
| Data | 0-N | Command payload |
| Checksum | 1 | `(-sum(header..data)) & 0xFF` |
#### Chunked Packet
Used when data exceeds the 14-byte chunk limit (SSID, password, handshake).
```
[0x52] [length] [instruction] [chunk_idx] [total_chunks] [data...] [checksum]
```
| Field | Size | Description |
|---|---|---|
| Header | 1 | Always `0x52` |
| Length | 1 | `len(data) + 6` (counts header through data + checksum) |
| Instruction | 1 | Command ID |
| Chunk Index | 1 | 1-based index of this chunk |
| Total Chunks | 1 | Total number of chunks |
| Data | 1-14 | Chunk payload (max `CHUNK_SIZE = 14` bytes) |
| Checksum | 1 | `(-sum(header..data)) & 0xFF` |
### Robot -> Client (Response)
Received as encrypted bytes on the notify characteristic, then AES-decrypted.
```
[0x51] [length] [instruction] [status] [data...] [checksum]
```
| Field | Size | Description |
|---|---|---|
| Header | 1 | Always `0x51` |
| Length | 1 | Payload length |
| Instruction | 1 | Echoed command ID |
| Status | 1 | `0x01` = success |
| Data | 0-N | Response payload |
| Checksum | 1 | `(-sum(header..data)) & 0xFF` |
Chunked responses (e.g. GET_SN) include `chunk_idx` and `total_chunks` after the instruction byte, same as the request format.
---
## Robot Connection Flow
```
1. BLE Scan -> Find device with name prefix Go2_ or G1_
2. GATT Connect -> Connect to the device
3. Detect Protocol -> Check services for NUS (V2) or FFE0 (V1)
4. Subscribe -> Enable notifications on the notify characteristic
5. Handshake -> Send chunked packet: instruction=0x01, data="unitree"
6. Verify -> Response status byte == 0x01 means success
```
### Handshake Detail
The handshake sends the string `"unitree"` (7 bytes) as a chunked packet with `idx=1, total=1`:
```
Plaintext: [0x52] [0x0D] [0x01] [0x01] [0x01] [u] [n] [i] [t] [r] [e] [e] [checksum]
header len=13 CMD idx total -------- "unitree" --------
```
This is then AES-encrypted and written to the write characteristic.
---
## Commands
| Command | ID | Format | Description |
|---|---|---|---|
| HANDSHAKE | `0x01` | Chunked | Auth with `"unitree"` |
| GET_SN | `0x02` | Simple (no data) | Request serial number (response is chunked) |
| WIFI_TYPE | `0x03` | Simple | Set WiFi mode: `0x01`=AP, `0x02`=STA |
| WIFI_SSID | `0x04` | Chunked | Send SSID (up to 14 bytes per chunk) |
| WIFI_PWD | `0x05` | Chunked | Send password (up to 14 bytes per chunk) |
| COUNTRY | `0x06` | Simple | Set country code: `[0x01] + "US\x00"` |
| GET_AP_MAC | `0x07` | Simple (no data) | Request AP MAC address |
| DISCONNECT | `0x08` | Simple (no data) | Disconnect BLE |
| HEARTBEAT | `0x0A` | Simple (no data) | Keep-alive |
### Response Parsing
- **GET_SN**: Comes as multiple chunked response packets. Reassemble chunks by index, decode as UTF-8.
- **GET_AP_MAC**: MAC bytes at `response[3:length-1]`, format as `XX:XX:XX:XX:XX:XX`.
- **WIFI_TYPE/SSID/PWD/COUNTRY**: Success if `response[3] == 0x01`.
---
## WiFi Configuration
Full WiFi setup sequence after handshake:
```
1. Set mode -> WIFI_TYPE (0x03) with 0x01 (AP) or 0x02 (STA)
2. Send SSID -> WIFI_SSID (0x04) chunked, 14 bytes per chunk, 50ms between
3. Send password -> WIFI_PWD (0x05) chunked, 14 bytes per chunk, 100ms between
4. Set country -> COUNTRY (0x06) with [0x01] + country_code + \x00
```
Each step waits for a success response before proceeding. The password step has a longer timeout (15s) as the robot applies the WiFi configuration.
> **Security note (CVE-2025-35027):** On Go2 firmware up to and including 1.1.11, the robot passes SSID and password to shell scripts via `system()` without sanitization, allowing command injection.
---
## Quick Reference
| Constant | Value |
|---|---|
| AES Key | `df98b715d5c6ed2b25817b6f2554124a` |
| AES IV | `2841ae97419c2973296a0d4bdfe19a4f` |
| Request header | `0x52` |
| Response header | `0x51` |
| Chunk size | 14 bytes |
| Handshake string | `"unitree"` |
| WiFi AP mode byte | `0x01` |
| WiFi STA mode byte | `0x02` |
| Success status | `0x01` |
For the V3 extension (`VERSION` 0xF1, `GCM_KEY` 0xF2 — G1 firmware ≥ 1.5.1 and Go2 firmware ≥ 1.1.15), see [bluetooth-v3.md](bluetooth-v3.md). For the BLE remote control and the WebRTC relay that forwards its inputs to the robot, see [remote-control.md](remote-control.md).

View File

@@ -0,0 +1,337 @@
# Bluetooth Protocol V3 — Unitree G1 + Go2
V3 is a small extension to the V1/V2 BLE protocol. First shipped on the Unitree G1 with firmware 1.5.1, then back-ported to the Unitree Go2 starting with firmware 1.1.15. It runs over the **same** GATT service and characteristics (FFE0 / FFE1 / FFE2 — see [bluetooth-v1-v2.md § Service UUIDs](bluetooth-v1-v2.md#service-uuids)) and coexists on the same connection: a single notify subscription receives both V1/V2 (encrypted) and V3 (unencrypted) frames, distinguished by their first byte.
V3 introduces two distinct categories of frames on top of V1/V2:
1. **Plaintext probe frames** — `0xF1` (device-ID) and `0xF2` (RSA-wrapped key blob). Magic-prefixed, unencrypted, used before any AES key is established.
2. **AES-128-GCM-wrapped command frames** — every legacy V1/V2 op (`GET_SN`, `GET_AP_MAC`, `WIFI_TYPE`, `WIFI_SSID`, `WIFI_PWD`, `COUNTRY`, …) is now AES-GCM-encrypted with a per-device 16-byte key. The plaintext payload inside the GCM envelope is the same `[0x52][len][op][…][cksum]` shape used on V1/V2, so the inner dispatcher is unchanged once decrypted.
The plaintext probe path:
- `0xF1` — historically named `VERSION` in the apk's enum, but the response carries the **device serial number** along with a small header (BLE module version byte + a flag byte). On V3-capable firmware (G1 ≥ 1.5.1, Go2 ≥ 1.1.15) this is what the apk uses to identify the robot before any GCM-encrypted command exchange — see [F1 Layout](#f1-version--device-id) below.
- `0xF2` — historically named `GCM_KEY`, but the response is **not** a plain AES key — it's a 256-byte RSA-encrypted blob (344 base64 chars) that the cloud server-side decrypts to derive the per-device 16-byte AES-128 key used for WebRTC `data2=3` authentication and for GCM-wrapping V1/V2 BLE commands.
Plaintext F1/F2 frames are not AES-CFB encrypted; they are framed with a fixed magic prefix so receivers can tell them apart from V1/V2 ciphertext. GCM-wrapped command frames have no magic prefix — they are demuxed from V1/V2 ciphertext by attempting GCM-decrypt first when an AES key is loaded.
## Firmware Compatibility
| Robot | Firmware | V3 supported |
|---|---|---|
| Unitree G1 | `≥ 1.5.1` | ✅ Yes |
| Unitree G1 | `< 1.5.1` | ❌ No (V1/V2 only) |
| Unitree Go2 | `≥ 1.1.15` | ✅ Yes |
| Unitree Go2 | `< 1.1.15` | ❌ No (V1/V2 only) |
A client targeting both robot families must:
1. Connect normally per [V1/V2 Connection Flow](bluetooth-v1-v2.md#robot-connection-flow).
2. Send a V3 request and treat a timeout as "V3 not supported" rather than as a failure. The robot silently drops V3 frames it does not recognize.
## Table of Contents
- [Magic Prefix](#magic-prefix)
- [Packet Format](#packet-format)
- [Commands](#commands)
- [GCM Command Frames](#gcm-command-frames)
- [Authenticated Handshake](#authenticated-handshake)
- [WiFi Configuration Flow](#wifi-configuration-flow)
- [GCM Key Usage](#gcm-key-usage)
- [Coexistence With V1/V2](#coexistence-with-v1v2)
- [Quick Reference](#quick-reference)
---
## Magic Prefix
Every V3 frame — request or response — starts with the 5-byte magic:
```
0x00 0x55 0x54 0x32 0x35 ("\0UT25")
```
The leading `0x00` is significant: V1/V2 frames begin with `0x52` (request) or `0x51` (response, after AES-CFB decryption). Any frame whose first byte is `0x00` on the notify characteristic is V3 and must **not** be passed through `AES.decrypt()` — doing so would produce garbage.
## Packet Format
V3 packets are sent as plaintext (no encryption). The checksum is the same scheme as V1/V2: `(-sum(bytes_so_far)) & 0xFF`, computed over every byte of the frame except the checksum itself, including the magic prefix.
### Client → Robot (Request)
```
[0x00] [0x55] [0x54] [0x32] [0x35] [command] [checksum]
```
| Field | Size | Description |
|---|---|---|
| Magic | 5 | Fixed `00 55 54 32 35` |
| Command | 1 | Opcode (`0xF1` = VERSION, `0xF2` = GCM_KEY) |
| Checksum | 1 | `(-sum(magic..command)) & 0xFF` |
Total: **7 bytes**, written unencrypted to the V1 (FFE2) or V2 (NUS TX) write characteristic.
### Robot → Client (Response)
The two opcodes use **different** response layouts:
**`0xF1` VERSION** — single, fixed-size frame (not chunked):
```
[0x00] [0x55] [0x54] [0x32] [0x35] [0xF1] [version_byte] [needShowNetSwitch] [cksum]
```
**`0xF2` GCM_KEY** — chunked. Each notification carries one chunk of a larger payload:
```
[0x00] [0x55] [0x54] [0x32] [0x35] [0xF2] [chunk_idx] [total_chunks] [data...] [checksum]
```
| Field | Size | Description |
|---|---|---|
| Magic | 5 | Fixed `00 55 54 32 35` |
| Command | 1 | Echoed opcode |
| Chunk Index | 1 | 1-based index of this chunk |
| Total Chunks | 1 | Total number of chunks |
| Data | 0-N | Chunk payload (ASCII characters of the base64 key) |
| Checksum | 1 | Per-chunk checksum |
> **Checksum note.** Frames are delivered inside fixed-MTU BLE notifications, which often carry padding past the logical frame end. The `(-sum(bytes)) & 0xFF` check therefore fails on real-world traces and the Unitree app skips it — trust the magic prefix and the opcode-specific layout instead.
### Reassembly
Buffer chunks per `command` until `len(received) == total_chunks`, then concatenate in index order. Decode the assembled bytes as UTF-8 and strip trailing `\x00` / whitespace.
```python
buckets: dict[int, dict[int, bytes]] = {}
def on_v3_frame(raw: bytes) -> tuple[int, str] | None:
if len(raw) < len(MAGIC) + 2 or raw[:5] != b"\x00UT25":
return None
cmd = raw[5]
if cmd == 0xF1:
return cmd, str(raw[6]) # F1: not chunked
if len(raw) < len(MAGIC) + 4:
return None
idx, total, data = raw[6], raw[7], raw[8:-1]
bucket = buckets.setdefault(cmd, {})
bucket[idx] = data
if total > 0 and len(bucket) >= total:
full = b"".join(bucket[i] for i in sorted(bucket)).rstrip(b"\x00").strip()
del buckets[cmd]
return cmd, full.decode("utf-8")
return None
```
## Commands
| Command | ID | Request payload | Response payload |
|---|---|---|---|
| VERSION | `0xF1` | (none) | Single frame: `[magic][F1][version_byte][needShowNetSwitch_flag][reserved(4)][sn_len(1)][sn_ascii][cksum]` — **the SN is embedded** length-prefixed at offset 12. Truncated to 7 chars under MTU=23; full 16 chars under MTU=104. Also pushed in response to the V1/V2 SECRET handshake. |
| GCM_KEY | `0xF2` | (none) | 344 ASCII chars of base64 (256 raw bytes, RSA-encrypted), delivered as 4 chunks. Truncated to 11 chars/chunk under MTU=23. |
### `0xF1` VERSION / Device-ID
Despite the name, this command's response carries the **device serial number** along with a small header (BLE module version + a flag byte). It's both a "do you speak V3?" probe *and* the canonical way to identify the connected robot before any AES key is established — the apk's `dogSn` field is set straight from this frame on V3-capable firmware (G1 ≥ 1.5.1, Go2 ≥ 1.1.15).
#### F1 Layout
```
[0..4] magic (5) 00 55 54 32 35
[5] opcode (1) F1
[6] version (1) 03 ← BLE module version (0x03 = V3)
[7] flags (1) bit 0 = needShowNetSwitch
[8..11] reserved (4) 00 00 00 00
[12] sn_len (1) length of the SN that follows (typically 0x10 = 16)
[13..] sn_ascii (N) ASCII SN bytes (e.g. "E21D6000PBF9ELG5")
[last] cksum (1) (-sum(prev bytes)) & 0xFF
```
Full size = `13 + sn_len + 1`. For a 16-char SN that's **30 bytes**, which fits comfortably under MTU=104 but is **truncated to 20 bytes (only 7 chars of SN) under default MTU=23**. Negotiate MTU first if you want the full SN out of this frame.
The frame is pushed unsolicited in response to the V1/V2 SECRET handshake — clients don't need to send `build_v3(0xF1)` explicitly to get it.
### `0xF2` GCM_KEY
> **Important: this is misnamed.** What the firmware returns is **not** an
> AES-128-GCM key — it's a **2048-bit RSA-encrypted blob** (256 bytes,
> base64-encoded as 344 chars including `==` padding) that wraps the actual
> per-device key plus device metadata. The cloud's `device/bindExtData`
> endpoint RSA-decrypts it server-side.
The reply is delivered as **4 chunked F2 frames** (`idx=1..4`, `total=4`).
The data carried per chunk depends on the negotiated BLE MTU:
| MTU | Notify size | Data per chunk | Reassembled length |
|---|---|---|---|
| 23 (default) | 20 B | 11 B | 44 chars (truncated) |
| 104 (apk default) | 101 B | 86 B | 344 chars (full) |
**You must negotiate MTU ≥ 32 to get the full payload** — the apk does
`exchange_mtu(104)` immediately after subscribing to notifications. Under
the default MTU=23 each chunk is truncated and the cloud subsequently
fails to RSA-decrypt the input with `"sk decode error"`.
The on-robot key file is `/unitree/etc/key/aes_key.bin` — but it's the
*encrypted* package, not the raw AES-128 key. The 16-byte key the cloud
returns to the client (and stores as `dev.key`) is what's actually used
for `data2=3` SDP authentication and for GCM-wrapping V1/V2 BLE commands.
## GCM Command Frames
Once an AES-128 key is loaded, every V1/V2 command op the firmware accepts (`GET_SN`, `GET_AP_MAC`, `WIFI_TYPE`, `WIFI_SSID`, `WIFI_PWD`, `COUNTRY`, …) is wrapped in an AES-GCM envelope before being written to the V1 (`FFE2`) characteristic. The robot's replies come back through the same channel using the same envelope.
### Envelope Layout
Both directions share one wire format:
```
[nonce_len(1)] [nonce(12)] [tag_len(1)] [tag(16)]
[cipher_len(1)] [ciphertext(N)] [outer_cksum(1)]
```
- `nonce_len` is always `0x0c` (12) and `tag_len` is always `0x10` (16). They are still on the wire because the firmware parses the lengths rather than hard-coding them.
- `outer_cksum` is `(-sum(body)) & 0xFF`, computed over every byte from `nonce_len` through the last ciphertext byte (i.e. excluding the checksum itself).
- `ciphertext` decrypts under `AES.GCM(key, nonce)` to a V1/V2 *inner* frame whose first byte is the request marker (`0x52` from app, `0x51` from robot).
### Inner-Frame Shapes
There are two inner shapes, mirroring V1's simple/chunked split:
```
simple = [0x52|0x51] [len] [op] [data...] [inner_cksum]
chunked = [0x52|0x51] [len] [op] [idx] [total] [data...] [inner_cksum]
```
`len` is the inner frame length including the trailing checksum. `inner_cksum` is `(-sum(prev bytes)) & 0xFF`.
### Which Ops Use Which Shape
The V3 firmware is strict about this — sending the wrong shape causes the request to be silently dropped:
| Op | Shape | Notes |
|---|---|---|
| `GET_SN` (`0x02`) | simple | empty data |
| `WIFI_TYPE` (`0x03`) | simple | 1-byte data: `0x01`=AP, `0x02`=STA |
| `WIFI_SSID` (`0x04`) | **chunked** | UTF-8 SSID; fits single chunk under MTU=104 |
| `WIFI_PWD` (`0x05`) | **chunked** | UTF-8 password; fits single chunk under MTU=104 |
| `COUNTRY` (`0x06`) | simple | `<country_utf8> ‖ <open_byte>` — see [WiFi flow](#wifi-configuration-flow) |
| `GET_AP_MAC` (`0x07`) | simple | empty data |
| `GET_TIME_3` (`0x0b`) | simple | handshake init from app, empty data |
| `CHECK_3` (`0x0c`) | simple | handshake reply, 8-byte BE timestamp+1 |
Even for ops where chunking isn't strictly necessary (the data is small and fits in one MTU), the firmware still pattern-matches on the inner-frame shape; SSID and password must use the chunked layout with `idx=1, total=1` or the request is dropped.
## Authenticated Handshake
Before the robot will respond to any GCM-wrapped command, the app must complete a tiny handshake that proves it holds the AES-128 key and is roughly time-synchronised:
1. App writes `GET_TIME_3` (op `0x0b`, simple inner, GCM-wrapped). Empty data payload.
2. Robot replies with `op=0x0b` carrying an **8-byte big-endian uint64 timestamp** (Unix seconds).
3. App writes `CHECK_3` (op `0x0c`, simple inner, GCM-wrapped) with the same timestamp **+1** as 8 BE bytes.
4. Robot replies with `op=0x0c, result=0x01` if the value matches; further GCM ops are now accepted.
The endianness is non-obvious — the apk reads it via `ByteBuffer.getLong()` (BE) but a few BLE wire references suggest little-endian; the firmware accepts BE only.
After this exchange the AES key is "armed" and `GET_SN` / `GET_AP_MAC` / WiFi ops will be answered. The handshake is done once per BLE connection.
## WiFi Configuration Flow
WiFi config is a five-step sequence, all GCM-wrapped:
1. `WIFI_TYPE` (simple, 1-byte data: `0x01`=AP, `0x02`=STA).
2. `WIFI_SSID` (chunked).
3. `WIFI_PWD` (chunked).
4. `COUNTRY` (simple) — payload is `<country_utf8> ‖ <open_byte>` where `open_byte=0x02` opens the AP / kicks the STA join, and `0x01` closes the AP. **This frame is not optional in AP mode** — without it the robot stores the credentials but never brings the AP up. It's also what triggers the actual radio change.
5. **Wait for unsolicited push** of `op=0x08, result=0x01` (the apk calls this `BleResultEvent type="9"`). This is the real success signal — COUNTRY's `0x06` ack only means "config accepted, opening AP / connecting now". Robot sends `op=0x08, result=<non-1>` on failure (e.g. `0x04` for an AP that couldn't start). 40-second timeout in the apk.
Each of steps 1–4 acks with `[0x51][len][op][result][inner_cksum]`. `result==0x01` means accepted, anything else is a per-step rejection.
```
APP → WIFI_TYPE (op 0x03) ──► robot ◄── ack op 0x03 result 0x01
APP → WIFI_SSID (op 0x04) ──► robot ◄── ack op 0x04 result 0x01
APP → WIFI_PWD (op 0x05) ──► robot ◄── ack op 0x05 result 0x01
APP → COUNTRY (op 0x06) ──► robot ◄── ack op 0x06 result 0x01 (config accepted)
◄── push op 0x08 result 0x01 (AP up / STA joined)
```
## GCM Key Usage
The key returned by `0xF2` is the secret used for `data2=3` WebRTC SDP authentication. The Unitree app derives a session nonce from the SDP offer/answer, encrypts it with this key under AES-128-GCM, and includes the ciphertext + tag in the signaling payload. The robot decrypts and validates the nonce before establishing the WebRTC peer connection.
Without the GCM key:
- WebRTC handshakes against V3-capable firmware (G1 ≥ 1.5.1, Go2 ≥ 1.1.15) will fail at the `data2=3` step.
- Older firmware (G1 `< 1.5.1`, Go2 `< 1.1.15`) does not require this auth — `con_notify` returns `data2=2` (static GCM key) and the SDP exchange is accepted unauthenticated.
The key is per-device and not user-secret in the strong sense (the robot freely hands it out over BLE to any client that completes the V1/V2 handshake), but it should be cached locally rather than re-fetched on every WebRTC session.
## Coexistence With V1/V2
The V1/V2 `HANDSHAKE` / `0x01` SECRET exchange is still required at connect time to bring the link into a usable state — V3 firmware answers it with an unsolicited F1 frame instead of the legacy AES-CFB reply, but the app must still send it.
After connect, the picture differs by firmware:
- **V3-capable firmware (G1 ≥ 1.5.1, Go2 ≥ 1.1.15).** Plaintext F1/F2 frames flow alongside GCM-wrapped command frames. The legacy V1/V2 AES-CFB path is **not** used for `GET_SN` / `GET_AP_MAC` / WiFi ops on this firmware — those have all moved into the GCM envelope. Sending a V1/V2 AES-CFB request here is silently dropped.
- **Legacy firmware (G1 < 1.5.1, Go2 < 1.1.15).** No V3 at all. `GET_SN` / `GET_AP_MAC` / WiFi flow stay on the V1/V2 AES-CFB path documented in [bluetooth-v1-v2.md](bluetooth-v1-v2.md).
A correctly-implemented notify handler therefore demuxes three classes of inbound frame:
```python
def on_notify(raw: bytes) -> None:
# 1) V3 plaintext (F1/F2): magic-prefixed, no encryption.
if len(raw) >= 5 and raw[:5] == b"\x00UT25":
handle_v3_plaintext(raw)
return
# 2) V3 GCM-wrapped command response — try this first when an AES key
# is loaded. Returns the inner V1/V2 frame on success.
if aes_key is not None:
plain = gcm_decrypt(raw, aes_key)
if plain is not None:
handle_v1_v2_inner(plain)
return
# 3) Legacy V1/V2 AES-CFB ciphertext.
plain = aes_cfb_decrypt(raw)
if len(plain) >= 4 and plain[0] == 0x51:
handle_v1_v2_inner(plain)
```
Routing V3 plaintext frames to the AES-CFB decryptor is a frequent porting bug — the decrypted output looks valid (random bytes), no exception is raised, and the framework silently drops the result because the first byte isn't `0x51`.
## Quick Reference
### Plaintext probe frames
| Constant | Value |
|---|---|
| Magic prefix | `00 55 54 32 35` (`"\0UT25"`) |
| Request length | 7 bytes |
| VERSION opcode | `0xF1` |
| GCM_KEY opcode | `0xF2` |
| Checksum | `(-sum(bytes)) & 0xFF`, includes magic |
| Encryption | None (plaintext) |
### GCM-wrapped command ops (inner `0x52`/`0x51` frame)
| Op | Name | Inner shape | Notes |
|---|---|---|---|
| `0x02` | GET_SN | simple | empty data |
| `0x03` | WIFI_TYPE | simple | `0x01`=AP, `0x02`=STA |
| `0x04` | WIFI_SSID | chunked | UTF-8 |
| `0x05` | WIFI_PWD | chunked | UTF-8 |
| `0x06` | COUNTRY | simple | `<country_utf8> ‖ <0x02 open / 0x01 close>` |
| `0x07` | GET_AP_MAC | simple | empty data |
| `0x08` | (push) | simple | unsolicited "AP up / STA joined" success — `result=0x01` |
| `0x0b` | GET_TIME_3 | simple | handshake init / robot timestamp reply (8 BE bytes) |
| `0x0c` | CHECK_3 | simple | handshake reply with timestamp+1 (8 BE bytes) |
### Misc
| Constant | Value |
|---|---|
| Min firmware | G1 1.5.1 / Go2 1.1.15 |
| Min MTU | 32 (negotiate 104 to match the apk) |
| AES key length | 16 bytes (32 hex chars) |
| Per-device AES key file (on-robot) | `/unitree/etc/key/aes_key.bin` (RSA-wrapped, not raw) |
| Cloud key source | `device/bind/list` → `dev.key` (32 hex chars) |
For scanning, V1/V2 commands, and WiFi configuration, see [bluetooth-v1-v2.md](bluetooth-v1-v2.md). For the BLE remote control and the WebRTC relay that forwards its inputs to the robot, see [remote-control.md](remote-control.md).

View File

@@ -0,0 +1,44 @@
# Bluetooth
Configure the robot's WiFi and pair the BLE remote control without the phone app. Open from the landing page → **Bluetooth** tile.
## Robot Provisioning
<p align="center">
<img src="../images/bluetooth_robot.png" width="70%" />
</p>
Scan, connect, fetch SN + AP MAC, configure WiFi (SSID, password, STA / AP mode, region). Adapter selector for switching between multiple HCI adapters (handy with USB dongles).
Protocol versions:
- **V1 / V2** — used by legacy firmware (Go2 `< 1.1.15`, G1 `< 1.5.1`). Standard Nordic UART or FFE0 service. See [bluetooth-v1-v2.md](bluetooth-v1-v2.md) for the full GATT layout and command schema.
- **V3** — G1 `≥ 1.5.1` and Go2 `≥ 1.1.15`. Adds two new opcodes: `VERSION` (`0xF1`) and `GCM_KEY` (`0xF2`). The UI gates V3 probes on the scanned BLE name (`/^(G1|Go2)[_\W]/i`); older firmware silently drops the probe so it fails soft (a few-second timeout). See [bluetooth-v3.md](bluetooth-v3.md) for the magic-prefix handshake and key-exchange details.
## Remote Control
<p align="center">
<img src="../images/bluetooth_remote.png" width="70%" />
</p>
Pair the Unitree BLE remote (`Unitree-*` advertising name) to read live joystick axes and button states, with an Hz counter showing the update rate.
Once paired, a gamepad icon appears in the control view's setting bar — click it to relay the remote's input to the robot over the WebRTC `rt/wirelesscontroller` channel:
<p align="center">
<img src="../images/bt_relay_button.png" width="50%" />
</p>
Full protocol details and frame layout in [remote-control.md](remote-control.md).
## Running the BLE Server
The dev `npm run start` script launches both Vite and the Python BLE backend together. To run them separately:
```bash
pip install -r server/requirements.txt
npm run ble-server # Python FastAPI BLE backend
npm run dev # Vite frontend (no BLE)
```
The frontend talks to the BLE server through Vite's `/ble-api/*` proxy, so CORS isn't a concern.

View File

@@ -0,0 +1,67 @@
# Connecting to the Robot
The UI talks to Go2 and G1 over the same WebRTC handshake the official Unitree mobile app uses. You pick the robot family on the landing page; the Connect screen then offers three transport modes.
<p align="center">
<img src="../images/landing.png" width="80%" />
</p>
## Family Selection
The landing page has two slots:
- **Account** — which family the cloud client signs as (`AppName: Go2` vs `AppName: G1`). Used for `device/bind/list`, tutorials, firmware listings.
- **Connect** — which family the local/remote WebRTC connector targets.
Both are persisted independently in `localStorage` so you can, for example, log in with a Go2 account and connect to a G1 on LAN.
## Connect Screen
<p align="center">
<img src="../images/connect_go2.png" width="48%" />
<img src="../images/connect_g1.png" width="48%" />
</p>
Three modes:
- **Local Network (STA-L)** — robot and client on the same LAN. Fill in the IP, or click **Scan** to auto-discover.
- **Access Point (AP)** — connect to the robot's own WiFi hotspot. IP is auto-filled (`192.168.12.1`).
- **Remote** — through Unitree's TURN/relay infrastructure. Requires an Unitree cloud session (Account tab).
The IP/SN inputs are namespaced per family in `localStorage` (`unitree_last_ip_<family>`, `unitree_last_sn_<family>`), so switching Go2 ↔ G1 doesn't trample your last-known IP.
## Network Scan
Click **Scan** to UDP-multicast for robots on the LAN. The dev server's Vite plugin runs the scanner internally, so no separate process is needed.
- **Go2** — multicast group `231.1.1.1`, port `10131`. No SN filter, returns every Go2 on the network.
- **G1 < 1.5.1** — group `239.255.1.1`, port `10134`. Broadcast scan returns all G1s.
- **G1 ≥ 1.5.1** — same group/port, but the firmware now filters by SN. The scan does a broadcast first, then a per-SN sweep over devices bound to your cloud account plus any SN you typed manually. Results are deduped by SN.
> Go2 firmware ≥ 1.1.15 also requires the per-device AES-128 key to complete WebRTC SDP exchange — see the AES-128 Key (`data2=3`) section below.
If your account already has the device bound, the SN field auto-populates from the scan; otherwise paste the SN from the back of the robot.
## G1 ≥ 1.5.1 / Go2 ≥ 1.1.15 — AES-128 Key (`data2=3`)
Starting with G1 firmware **1.5.1** (back-ported to Go2 firmware **1.1.15**), the LAN signaling reply (`con_notify`) returns `data2=3`, meaning the embedded RSA public key is wrapped under a **per-device AES-128-GCM key**. Older firmware (G1 < 1.5.1, Go2 < 1.1.15) returns `data2=2` (static GCM key, handled transparently).
The key is per-device and stable across re-pairings. The UI gets it three ways, in order:
1. **Cloud-primed cache** — when you log in to Account, `device/bind/list` is fetched and every `dev.key` is stored as `(sn → key)` in `localStorage` under `unitree_aes_keys_v1`. Subsequent connects pull from there silently. If `dev.key` is missing for a device, open Account → device tile → **Details** → "Refresh AES Key" and paste the 344-char extData blob from the BT page; the cloud derives the key and persists it on the binding.
2. **Manual prompt** — if no cached key matches, a modal asks for the 32-hex AES key for that SN. The entry is cached for next time.
3. **Retry** — wrong key flushes the cache and reprompts (up to 3 attempts) with a "previous key didn't decrypt" banner so you don't keep entering the same wrong value.
Connection logs (`[g1] AES-128 key from localStorage cache (SN ...)` / `[go2] AES-128 key (prompted) — cached for SN ...`) tell you which path was taken.
## Tutorials
The hub pulls the same tutorial videos the mobile app shows. The query is `tutorial/list?appName=<family>&type=<model>` — `appName` is the family literal (`Go2`/`G1`), not the HTTP `AppName` signing header (which uses `Go2`/`B2` on the cloud side).
## Browser Support
| Browser | Status |
|---------|--------|
| Chrome | Tested, fully working |
| Firefox | Experimental — WebRTC data channel timing differs |
| Safari | Not tested |

View File

@@ -0,0 +1,52 @@
# Control View
The control view is the live operating screen — joystick + actions + camera + 3D scene.
<p align="center">
<img src="../images/webview_go2.png" width="48%" />
<img src="../images/webview_g1.png" width="48%" />
</p>
## Layout
- **3D viewport** — Go2 / G1 model with live joint angles, lidar spinning animation, and SLAM voxel overlay (Go2).
- **Camera feed (PIP)** — draggable picture-in-picture; click to swap with the 3D scene.
- **Dual joysticks** — left moves, right rotates.
- **Action bar** — carousel of robot actions and modes. Customizable order.
- **Setting bar (top-right)** — Radar, LiDAR, Volume, Light. With a BLE remote connected, a gamepad icon also appears here for relay (see [bluetooth.md](bluetooth.md)).
- **Theme toggle** — floating sun/moon in the upper-right; preference is persisted.
## Actions and Modes
Actions and modes are issued as MCF sport API IDs. Tested against Go2 firmware v1.1.11; G1 uses an overlapping subset.
| Action | ID | Mode | ID |
|--------|----|------|----|
| Roll Over | 1021 | Free Walk | 2045 |
| Stretch | 1017 | Pose | 1028 |
| Shake Hand | 1016 | Run | 1011 |
| Heart | 1036 | Walk Stair | 1049 |
| Pounce | 1032 | Static Walk | 1061 |
| Jump Forward | 1031 | Endurance | 1035 |
| Greet | 1029 | Leash | 2056 |
| Dance 1 | 1022 | Hand Stand | 2044 |
| Dance 2 | 1023 | Free Avoid | 2048 |
| Front Flip | 1030 | Bound | 2046 |
| Back Flip | 2043 | Jump | 2047 |
| Left Flip | 2041 | Stand | 1006 |
| Damping | 1001 | Cross Step | 2051 |
| Sit Down | 1009 | Rear Stand | 2050 |
| Crouch | 1005 | Rage Mode | 2059 |
| Lock On | 1004 | | |
## BLE Remote Relay
When a Unitree BLE remote is paired (see [bluetooth.md](bluetooth.md)), a gamepad icon appears in the setting bar.
<p align="center">
<img src="../images/bt_relay_button.png" width="60%" />
</p>
Click it to forward the remote's joystick axes and button states to the robot over `rt/wirelesscontroller`. The on-screen virtual joysticks hide while relay is active. Hz counter on the BT page shows the live update rate.
Protocol details: [remote-control.md](remote-control.md).

View File

@@ -0,0 +1,147 @@
# Error handling
Runtime fault visualization for the connected robot (Go2 and G1) — surfaces
firmware-reported faults the moment they appear, keeps an authoritative active
set in memory, and exposes three ways to view them: a NavBar badge, a
click-anchored popover, and a full-screen Errors page (also reachable from
the Hub).
## Wire protocol
Faults arrive on the WebRTC **data channel**, not on a topic. Three message
types share one payload shape — an array of triples
`[timestamp, error_source, error_code]`:
| `type` | Direction | `data` | Meaning |
|---------------|--------------|-------------------------------------|-------------------------------------------------------------------------|
| `errors` | Robot → app | `[[ts, src, code], …]` | Authoritative snapshot of every fault currently active. Sent periodically and immediately after validation. |
| `add_error` | Robot → app | `[ts, src, code]` *(single triple)* | A new fault just appeared. |
| `rm_error` | Robot → app | `[ts, src, code]` *(single triple)* | An existing fault just cleared. |
Field types:
- **`timestamp`** — unix seconds (integer). The "appeared at" time of the fault.
- **`error_source`** — integer category, see the [source table](#sources).
- **`error_code`** — single bit value. One active fault per entry — never
packed bitmasks. Multiple simultaneous faults in the same source arrive as
multiple triples. Go2 codes stay inside 9 bits (max `0x100`); G1 widens the
field to 28+ bits (max `0x10000000` seen) — still well inside `Number.MAX_SAFE_INTEGER`,
so no `BigInt` handling is needed.
A typical first `errors` message after validation looks like:
```json
{
"type": "errors",
"data": [
[1715890123, 300, 16],
[1715890145, 200, 1]
]
}
```
Decoded: "Winding overheating (motor)" since 21s ago, and "Rear left fan jammed"
since just now.
## Reconciliation model
The store treats `errors` as the source of truth and `add_error` / `rm_error`
as deltas that keep the UI in lock-step between snapshots:
- **`errors`** → `replaceAll(triples)`. Existing entries keep their original
timestamp so the "appeared at" label doesn't jitter on resync.
- **`add_error`** → insert. **No-op if the `(source, code)` key already
exists**, so a snapshot/delta race during reconnect doesn't double-fire.
- **`rm_error`** → remove by `(source, code)`. No-op if not present.
- **WebRTC disconnect** → store is cleared.
This lets the UI react in real time (a new fault toasts the instant the firmware
detects it) without ever drifting indefinitely — the next snapshot resyncs the
full active set.
## UI surfaces
| Surface | Where | Behavior |
|---|---|---|
| **Toast** | Bottom-right, floating | Slides in on every `add_error`. Snapshot replays are silent so reconnects don't spam. 4.5s auto-dismiss; click to dismiss early. |
| **NavBar badge** | Top-right of the Control screen | Red warning triangle + count chip. Visible only when active count > 0. Click opens the popover. |
| **Floating badge** | Top-right on Hub / Status / Services / Account / BT screens | Same icon + count, anchored as a persistent floating icon. Hidden on the Control screen (the NavBar badge takes over). |
| **Popover** | Anchored under whichever badge was clicked | Header with active count, up to four rows visible, scroll for more. Shows code label · source · code id · relative time. Closes on outside-click or when the active set empties. |
| **Errors page** | Reached via the Hub's *Errors* button | Full-screen grouped view. Faults are bucketed by source category with a section header. Each row shows the code label, a hex `0x…` chip with the bit value, and a two-line time stack (absolute clock + relative). |
## Sources
| ID | Family | Label | Notes |
|---------------|----------|--------------------------------------|--------------------------------------------------------|
| 100 | Go2 / G1 | Communication firmware error | DDS, distribution switch, MCU, motor link, battery I²C |
| 200 | Go2 / G1 | Communication firmware error | Cooling fans (front / rear-left / rear-right) |
| 300 | Go2 / G1 | Motor malfunction | Overcurrent / overvoltage / encoder / thermals |
| 301–399 | G1 | Motor *N* (= source − 300) | Same code semantics as 300, attributed to one joint |
| 400 | Go2 / G1 | Radar malfunction | LiDAR motor, point-cloud integrity, serial port, dirt |
| 500 | Go2 / G1 | UWB malfunction | Serial open, info retrieval |
| 600 | Go2 / G1 | Motion Control | Software thermal + low-battery protections |
| 700 | Go2 / G1 | BMS error | Battery management faults |
| 800 | G1 | Chassis error | Humanoid-specific physical fault |
| 900 | G1 | Power distribution switch anomaly | Power-rail fault (source label only — no code labels shipped) |
| 1000 | G1 | Emergency Stop | E-stop triggered |
| 3000–3999 | G1 | Motor *N* (= source mod 100) | Alternate per-joint addressing — same 300 code bits |
### Per-motor synthesis
G1 reports per-joint motor faults using two source-number conventions:
`301 + jointIdx` and `3000 + jointIdx`. Both ranges are resolved at lookup
time by [`lookupSource()`](../src/protocol/errors-catalog.ts) — they return
`"Motor N"` and share source `300`'s code-bit catalog via
[`lookupCode()`](../src/protocol/errors-catalog.ts), so the same overcurrent
/ encoder / overheat bits decode correctly whether the fault is attributed
generically (source 300) or to a specific joint.
## Sample codes
Codes are looked up by `(source, hex(code))`. A few representative entries:
| Source | Code (decimal / hex) | Family | Label |
|--------|-----------------------------------|----------|------------------------------------|
| 100 | 1 / `0x1` | Go2 / G1 | DDS message timeout |
| 100 | 16 / `0x10` | Go2 / G1 | Battery communication error |
| 100 | 128 / `0x80` | Go2 / G1 | Motor communication error |
| 200 | 1 / `0x1` | Go2 / G1 | Rear left fan jammed |
| 200 | 4 / `0x4` | Go2 / G1 | Front fan jammed |
| 300 | 1 / `0x1` | Go2 / G1 | Overcurrent |
| 300 | 4 / `0x4` | Go2 / G1 | Driver overheating |
| 300 | 16 / `0x10` | Go2 / G1 | Winding overheating |
| 300 | 32 / `0x20` | Go2 / G1 | Encoder abnormal |
| 300 | 256 / `0x100` | Go2 / G1 | Motor communication interruption |
| 300 | 4 096 / `0x1000` | G1 | Command anomaly |
| 300 | 65 536 / `0x10000` | G1 | Status anomaly |
| 300 | 16 777 216 / `0x1000000` | G1 | Motor humidity anomaly |
| 300 | 33 554 432 / `0x2000000` | G1 | Encoder remote |
| 300 | 67 108 864 / `0x4000000` | G1 | MOS almost overheat |
| 300 | 134 217 728 / `0x8000000` | G1 | Encoder close |
| 300 | 268 435 456 / `0x10000000` | G1 | Winding almost overheat |
| 400 | 1 / `0x1` | Go2 / G1 | Motor rotate speed abnormal |
| 400 | 2 / `0x2` | Go2 / G1 | PointCloud data abnormal |
| 400 | 16 / `0x10` | Go2 / G1 | Abnormal dirt index |
| 500 | 1 / `0x1` | Go2 / G1 | UWB serial port open abnormal |
| 600 | 4 / `0x4` | Go2 / G1 | Overheating software protection |
| 600 | 8 / `0x8` | Go2 / G1 | Low battery software protection |
Unknown `(source, code)` pairs fall back to `Source <id>` and `Code 0x<hex>` so
the UI keeps working when the firmware introduces a new fault.
## Where this lives in the source
- [`src/protocol/errors-catalog.ts`](../src/protocol/errors-catalog.ts) — source/code label tables + `decodeError()` helper.
- [`src/protocol/error-store.ts`](../src/protocol/error-store.ts) — observable active-set, reconciliation rules.
- [`src/protocol/data-channel.ts`](../src/protocol/data-channel.ts) — wire dispatcher (`errors`/`add_error`/`rm_error` → store).
- [`src/ui/components/errors-badge.ts`](../src/ui/components/errors-badge.ts) — badge (inline + floating flavours).
- [`src/ui/components/errors-popover.ts`](../src/ui/components/errors-popover.ts) — anchored dropdown.
- [`src/ui/components/errors-page.ts`](../src/ui/components/errors-page.ts) — grouped full-screen view.
- [`src/ui/components/error-toast.ts`](../src/ui/components/error-toast.ts) — transient new-fault notification.
## Adding a new code
When the firmware ships a new fault bit, add the label to `CODE_LABELS` in
[`errors-catalog.ts`](../src/protocol/errors-catalog.ts) using the key
`<source>_<lowercase-hex-code>` (no `0x` prefix, no padding) — for example
`300_200` for source 300 bit value 512. New sources go in `SOURCE_LABELS`.

View File

@@ -0,0 +1,242 @@
# LiDAR / Voxel Map — Unitree Go2
The control view renders the Go2's LiDAR output as a live 3D voxel map (SLAM). Compressed voxel packets arrive over WebRTC, are decoded off the UI thread by a WASM kernel, and displayed as a triangle mesh in Three.js alongside the 3D robot model.
## Table of Contents
- [Topics](#topics)
- [Enable / Disable](#enable--disable)
- [Data Flow](#data-flow)
- [Compressed Packet Format](#compressed-packet-format)
- [WASM Decoder (`libvoxel.wasm`)](#wasm-decoder-libvoxelwasm)
- [Rendering Pipeline](#rendering-pipeline)
- [Robot Radar Animation](#robot-radar-animation)
- [Performance](#performance)
- [Files](#files)
---
## Topics
All LiDAR traffic travels on the WebRTC DataChannel under the `rt/utlidar/*` namespace.
| Topic | Direction | Purpose |
|---|---|---|
| `rt/utlidar/switch` | client → robot | `'ON'` / `'OFF'` string toggles the LiDAR stream |
| `rt/utlidar/voxel_map_compressed` | robot → client | Compressed voxel map frames (ArrayBuffer) |
| `rt/utlidar/lidar_state` | robot → client | LiDAR health/status metadata (human-readable string) |
| `rt/utlidar/robot_pose` | robot → client | Robot odometry used as map origin |
Constants are defined in [src/protocol/topics.ts](../src/protocol/topics.ts) (`RTC_TOPIC.LIDAR_ARRAY`, `LIDAR_STATE`, `ROBOT_ODOM`). Subscription happens in `App.enableVideoAndSubscribe()`.
---
## Enable / Disable
The LiDAR has to be explicitly powered up — it doesn't stream by default.
**Enable:** `setLidarToggle(true)` publishes the string `"ON"` to `rt/utlidar/switch` **five times at 100 ms intervals** (firmware occasionally drops the first packet; the repeat is a reliability fix) and starts the radar spinning animation on the 3D model.
**Disable:** publishes `"OFF"` once, stops the radar spin, and clears the voxel map via `voxelMap.clear()`.
The toggle is surfaced via the LiDAR icon in the control view's setting bar (between Radar and Volume). The state is **not** persisted across reconnects — each WebRTC session starts with LiDAR off.
---
## Data Flow
```
Robot LiDAR
└─► WebRTC DataChannel (rt/utlidar/voxel_map_compressed)
└─► DataChannelHandler (onmessage)
└─► VoxelMap.update(ArrayBuffer, origin, resolution)
└─► voxel-worker (postMessage) [off main thread]
└─► libvoxel.wasm._generate(...)
├─► positions (Uint8Array)
├─► uvs (Uint8Array)
└─► indices (Uint32Array)
└─► Three.js BufferGeometry + MeshBasicMaterial
└─► scene.add(mesh)
```
Each incoming frame creates a fresh mesh. The previous mesh is disposed so the map effectively "replaces" on every successful decode.
---
## Compressed Packet Format
What JavaScript hands to the decoder (alongside the raw bytes from the WebRTC message):
```ts
{
data: ArrayBuffer; // LZ4-compressed voxel blob
resolution: number; // voxel cell size in meters (e.g. 0.1)
origin: [number, number, number]; // world offset for the map
}
```
`resolution` and `origin` come from sibling fields in the envelope. `origin` updates as the robot moves, so the mesh stays in world coordinates each frame.
### Compression: LZ4 block format
The payload is **LZ4 block format**. The uncompressed size is provided out-of-band in the packet envelope (`src_size`), so the decoder is a direct call to `LZ4_decompress_safe(src, dst, srcSize, dstCapacity)`. No LZ4 frame header — each packet is a standalone block.
Equivalent Python reference (useful for a standalone decoder):
```python
import lz4.block
decompressed = lz4.block.decompress(compressed_data, uncompressed_size=src_size)
```
Our WASM kernel [`public/libvoxel.wasm`](../public/libvoxel.wasm) (9 520 bytes) embeds the LZ4 decoder alongside the geometry generator, so JS only exchanges opaque pointers with it.
### Decompressed payload layout — 3D occupancy bitfield
After LZ4 decompress, the buffer is a flat `uint8[]` array interpreted as a **3D occupancy grid**, bit-packed along the X axis, 128×128×Z voxels per map.
**Per-byte addressing** (exactly as implemented in the Python reference):
```
byte_index → (x, y, z):
z = byte_index // 0x800 # 0x800 = 2048 bytes per Z slice
n = byte_index % 0x800
y = n // 0x10 # 0x10 = 16 bytes per Y row
x_base = (n % 0x10) * 8
# within the byte, bit 7 (MSB) = +0, bit 0 (LSB) = +7
for bit in 0..7:
if byte & (1 << (7 - bit)):
emit voxel at (x_base + bit, y, z)
```
**Dimensions:**
| Axis | Extent | Why |
|---|---|---|
| X | 128 | `16 bytes per row × 8 bits` |
| Y | 128 | `0x800 / 0x10 = 128 rows per slice` |
| Z | variable | `decompressed_size / 0x800` slices — grows with map height |
**Bit convention:** MSB-first within each byte (`bit 0 (MSB) = x_base+0`, `bit 7 (LSB) = x_base+7`). A set bit = the voxel at that grid cell is **occupied** (hit by the LiDAR).
**Grid → world conversion** (same formula as the Python `bits_to_points`):
```
world_xyz = grid_xyz * resolution + origin
```
- `resolution` is typically `0.05 m` or `0.1 m` per voxel (depends on firmware config — the frontend just uses what the envelope provides)
- `origin` is the world-space corner of the grid, updated as the robot moves
### From occupancy bits to renderable geometry
The WASM kernel walks the occupancy bitfield and emits one quad per occupied voxel (face-per-voxel, not merged). For each face it writes:
- 4 vertices × 3 `uint8` grid coords (scaled client-side by `resolution`)
- 4 vertices × 2 `uint8` UV coords for the height-gradient texture lookup
- 6 `uint32` indices (two triangles per quad)
Plus a scalar `zNormalized` per vertex used by the kernel to drive the UV row (so color encodes height).
### Output geometry buffers (kernel → worker → main thread)
For a packet with `faceCount = N`:
| Buffer | Size bytes | Layout |
|---|---|---|
| `positions` | `N × 12` | 4 verts/face × 3 coords × **uint8** (grid-space, scale by `resolution`) |
| `uvs` | `N × 8` | 4 verts/face × 2 coords × **uint8** (2D texture lookup) |
| `indices` | `N × 24` | 6 indices/face × **uint32** (2 triangles, 3 indices each) |
So each `face` = one voxel wall quad (4 vertices, 2 triangles). Max packet size supported: **240,000 faces** per frame (set by the `_malloc` capacities in [voxel-worker.ts:31-38](../src/workers/voxel-worker.ts#L31-L38) — `2,880,000 / 12 = 240k`).
Per-face WASM wall-clock on the worker thread is sub-millisecond at typical packet sizes; throttling (see [Performance](#performance)) caps the main-thread geometry rebuild at ~6.7 Hz.
---
## WASM Decoder (`libvoxel.wasm`)
**Location:** [public/libvoxel.wasm](../public/libvoxel.wasm) (loaded by fetch inside the worker)
**Loader:** [src/workers/voxel-worker.ts](../src/workers/voxel-worker.ts) — a standard dedicated Web Worker that instantiates the module with Emscripten-style imports (memcpy, heap resize).
**Exports:**
| Symbol | Signature | Role |
|---|---|---|
| `_generate` | `(inputPtr, inputLen, decompressBufferSize, decompressBuffer, decompressedSize, positions, uvs, indices, faceCount, pointCount, zNormalized) → void` | Main kernel: decompresses + emits triangulated geometry |
| `_malloc` | `(size) → ptr` | Allocate inside WASM linear memory |
| `_free` | `(ptr) → void` | Release allocation |
| `c` | `WebAssembly.Memory` | Shared linear memory (read back as `Uint8Array`/`Uint32Array` views to extract output arrays) |
The output arrays are produced inside WASM memory; the worker copies them into typed arrays and `postMessage`'s the result back to the main thread (transferring the ArrayBuffers to avoid a copy).
---
## Rendering Pipeline
Implemented in [src/ui/scene/voxel-map.ts](../src/ui/scene/voxel-map.ts).
- **Geometry:** `THREE.BufferGeometry` with:
- `position` attribute — `Uint8Array`, 3 components per vertex (voxel-grid coords)
- `uv` attribute — `Uint8Array`, 2 components
- `index` — `Uint32Array`
- **Material:** `THREE.MeshBasicMaterial` (no lighting — the color is already baked) with a `NearestFilter` texture at [public/models/axisColor4.png](../public/models/axisColor4.png).
- **Coloring:** height-based via UV lookup. The WASM kernel computes a normalized Z per vertex and writes it into the UV so the texture pixel at that row gives the final color. Swapping the texture changes the palette.
- **Transform:** the mesh is scaled by `resolution` and translated to `origin`, so the visualization sits in the same world space as the Go2 model.
- **Culling:** `frustumCulled = false` — the map can be very wide and camera-frustum tests have falsely clipped large meshes before.
The voxel map and robot model share the same Scene3D instance; the PIP-swap feature just toggles which one is "featured" vs "thumbnail".
---
## Robot Radar Animation
A dummy skeletal bone named `RadarBone` in the Go2 GLTF rotates **1 full revolution per second** while LiDAR is on. Implemented in [src/ui/scene/robot-model.ts](../src/ui/scene/robot-model.ts) via `requestAnimationFrame`. The spin is purely cosmetic — there's no physical LiDAR position in the model; it mirrors the APK's "radar is active" visual cue.
`setRadarSpinning(enabled)` is called from the same LiDAR toggle handler so the animation stays in sync with the actual stream.
---
## Performance
| Aspect | Detail |
|---|---|
| Worker thread | Single dedicated worker — WASM decode off the UI thread, main thread only rebuilds the Three.js BufferGeometry |
| Throttle | `VoxelMap.update()` coalesces at 150 ms — if packets arrive faster, only the latest queued payload is processed. Older ones are discarded so the map never lags |
| Transfers | Typed-array buffers are sent via `postMessage` with `transfer: [...]` (zero-copy ownership handoff) |
| Allocation | Each decode allocates a fresh `BufferGeometry`. The previous mesh is disposed (material kept, it's reusable) so memory stays flat |
| GC pressure | High-rate voxel frames can spike if `resolution` is too small or the world is huge — typical rates (~5-10 Hz post-throttle) work well |
The mesh is **not** instanced; the decoder outputs cube-face triangles directly for each occupied voxel in a single draw call.
---
## Files
| File | Role |
|---|---|
| [src/ui/app.ts](../src/ui/app.ts) | Subscription + toggle wiring; routes topic payloads to `VoxelMap` |
| [src/ui/scene/voxel-map.ts](../src/ui/scene/voxel-map.ts) | `VoxelMap` class: worker owner, mesh builder, throttle, clear |
| [src/workers/voxel-worker.ts](../src/workers/voxel-worker.ts) | Web Worker: loads `libvoxel.wasm`, runs `_generate`, posts back typed arrays |
| [public/libvoxel.wasm](../public/libvoxel.wasm) | Emscripten-compiled decoder (opaque binary) |
| [public/models/axisColor4.png](../public/models/axisColor4.png) | Height-gradient lookup texture |
| [src/ui/scene/robot-model.ts](../src/ui/scene/robot-model.ts) | Go2 GLTF model, `RadarBone` spin |
| [src/ui/scene/scene.ts](../src/ui/scene/scene.ts) | Three.js scene owner (shared by robot + voxel map) |
| [src/protocol/topics.ts](../src/protocol/topics.ts) | `RTC_TOPIC.LIDAR_ARRAY` / `LIDAR_STATE` / `ROBOT_ODOM` string constants |
| [src/protocol/data-channel.ts](../src/protocol/data-channel.ts) | `subscribe(topic)` / `publish(topic, data)` helpers |
---
## Quick Reference
| Item | Value |
|---|---|
| Topic (data) | `rt/utlidar/voxel_map_compressed` |
| Topic (state) | `rt/utlidar/lidar_state` |
| Topic (pose) | `rt/utlidar/robot_pose` |
| Topic (toggle) | `rt/utlidar/switch` (payload: `"ON"` / `"OFF"`) |
| Toggle reliability | `"ON"` sent 5× at 100 ms |
| WASM kernel | `_generate(...)` in `libvoxel.wasm` |
| Throttle | 150 ms (coalesces) |
| Radar spin | 1 rev/s |
| Default resolution | 0.1 m per voxel |

View File

@@ -0,0 +1,598 @@
# R1 Action List (FSM states)
Every persistent posture, gait, and motion on the Unitree **R1** is a single FSM
state, selected by one API. This is different from the Go2 sport API (many
distinct command IDs) — on R1 you send **one api_id and change the `fsm_id`**.
Most of the table below is **not exposed by the official Explorer app**.
## The API
| | Value |
|---|---|
| **DDS/WebRTC service** | `sport` (`LOCO_SERVICE_NAME`) |
| **Request topic** | `rt/api/sport/request` (`RTC_TOPIC.SPORT_MOD`) |
| **Set state** | api_id **`7101`** — `ROBOT_API_ID_LOCO_SET_FSM_ID` |
| **Read state** | api_id **`7001`** — `ROBOT_API_ID_LOCO_GET_FSM_ID` |
| **Set velocity** | api_id **`7105`** — `ROBOT_API_ID_LOCO_SET_VELOCITY` |
| **Set motion** (sub-clip) | api_id **`7108`** — `LocoServer::SetMotion` (see "Triggering actions within a state") |
| **Parameter** | JSON `{"data": <value>}` — except `7105`, which takes `{"velocity":[vx,vy,omega],"duration":<s>}`, and `7108`, which takes an **array** (below) |
| **Server handler** | `LocoServer::SetFsmId` → `changeState(fsm_id)` |
> **api_id 7108 is per-service.** On the **sport/loco** topic (`rt/api/sport/request`)
> 7108 = `SetMotion`. The *same number* on the **arm** topic (`rt/api/arm/request`)
> is an arm-teach command — they don't collide because api_id is scoped per service.
> The R1 loco service registers exactly `7001 GetFsmId, 7101 SetFsmId, 7105
> SetVelocity, 7108 SetMotion`; the 7108 handler logs `"Call SetMotion ..."`.
R1 reuses the G1 `7xxx` api_id scheme. The current mode is echoed back in
`SportModeState.fsm_id` on `rt/lf/sportmodestate`.
Example request parameter to start Kung Fu:
```json
{ "data": 607 }
```
## FSM state table
Policy paths below are relative to the motion net root; states without a policy
are analytic controllers (no neural net).
### Basic / static states
| fsm_id | Internal name | App label | Policy | Notes |
|------:|---------------|-----------|--------|-------|
| 0 | `ZeroTorque` | Zero Torque | — (analytic) | motors free |
| 1 | `Damping` | Damping | — (analytic) | soft-stop / relax |
| 4 | `Stance` | **Lock** (Preparation) | — (analytic) | app calls this "Lock" |
| 5 | `Keep` | Keep | — (analytic) | hold current pose |
| 6 | `MoveTo` | Move To | — (analytic) | |
| 7 | `SitDown` | Sit Down | — (analytic) | |
| −1 | `Root` | — | — | internal super-state, not selectable |
| −11 | `Init` | — | — | internal |
| −12 | `Static` | — | — | internal super-state |
### Recovery / posture
| fsm_id | Internal name | Policy | Meaning |
|------:|---------------|--------|---------|
| 701 | `Qishen` (起身) | `qishen/r1_air_tangqi_faceup.onnx` + `qishen/r1_air_tangqi_facedown.onnx` | **Stand up from ground** — face-up **and** face-down policies, so it recovers from either back or belly |
| 702 | `Tangxia` (躺下) | `tangxia/r1_tangxia_qili.onnx` | **Lie down** |
On the BLE remote both 701 and 702 are bound to the same combo
(`R1_hold` + `X_long`), so it behaves as a **lie ↔ stand toggle**.
### Locomotion / motion
| fsm_id | Internal name | Policy | Notes |
|------:|---------------|--------|-------|
| 800 | `Motion` | — | generic motion super-state |
| 804 | `MimicActor` | `mimic_actor/model.onnx` | mimic / teleop — **gone on current firmware** (code 1003) |
| 805 | `MimicPunch` | `mimic_punch/model.onnx` | mimic punch (2 policies) — **gone on current firmware** (code 1003) |
| 811 | `AmpMotion22Dof` | `amp_motion_22dof/actor.onnx` | **Run** (app label) |
| 812 | `AmpMotion` | `amp_motion/actor.onnx` | AMP motion, 24-dof |
| 813 | `Locomotion24DofStraightKnee` | `locomotion_24dof_straight_knee/actor.onnx` | walk, straight knee |
| 814 | `Locomotion24DofWalk` | `locomotion_24dof_walk/actor.onnx` | walk, 24-dof |
| 815 | `AmpLocomotion` | `amp_locomotion/policy_10k.onnx` | AMP locomotion |
| 816 | `ArmSdkLocomotion` | `arm_sdk_locomotion/policy_32.5k.onnx` | locomotion + arm-SDK — **armsdk white-list only** |
| 830 | `Locomotion20Dofs` | `locomotion_20dofs/25000amp6_cwdata_011.onnx` | 20-dof locomotion |
| 831 | `LocomotionArmsdk` | `locomotion_armsdk/r1air_armsdk_loco23000.onnx` | locomotion + arm-SDK — **armsdk white-list only** |
### Dances / martial arts
| fsm_id | Internal name | Policy | App label |
|------:|---------------|--------|-----------|
| 601 | `dance1_subject2_segment1` | `dances/dance1_subject2_niuniuwu/policy_80k.onnx` | Dance 1 |
| 602 | `dance1_subject2_segment2_hard` | `dances/dance1_subject2/dance1_subject2_hard_80k-120k.onnx` | Dance 2 |
| 603 | `dance1_subject2_segment3_hard` | `dances/dance1_subject2/dance1_subject2_hard_80k-120k.onnx` | Dance 3 |
| 604 | `niuniu` | `dance_niuniu/policy.onnx` | Niuniu Dance |
| 605 | `weibosite` | `dance_weibosite/policy.onnx` | Weibosite Dance |
| 606 | `weibosite_CJJ` | — | *(disabled)* |
| 607 | `gongfu_2` (功夫) | `dances/gongfu_jiequandao/policy_80k.onnx` | **Kung Fu** |
| 608 | `jiequandao` (截拳道) | `dances/gongfu_jiequandao/policy_80k.onnx` | **Jeet Kune Do** |
Note: Kung Fu (607) and Jeet Kune Do (608) share the same policy
(`gongfu_jiequandao/policy_80k.onnx`) but play different motion segments.
## Reachability
There are **two independent lists**, and they do different jobs. Confusing them
is the easiest way to misread this firmware.
### 1. `set_fsm_white_list` / `set_fsm_black_list` — what gates *switching*
These live in the **current** state's own config and name the states you may
switch **to** from there. An empty white list means "anything except the black
list". This is the only thing that can reject a `SetFsmId`:
| While in | May switch to |
|---|---|
| **Stance** (4, "Lock"), SitDown, MoveTo, Qishen, Tangxia, dances, **MimicActor, MimicPunch** | *(empty — anything)* |
| Damping (1), Keep (5) | ZeroTorque, Damping, Stance, Keep |
| ZeroTorque (0) | Stance, Keep, Damping |
| AmpMotion22Dof (811, "Run") | Damping, Stance, Tangxia |
| AmpLocomotion (815), Locomotion20Dofs (830) | Damping, Stance, Tangxia, LocomotionArmsdkGait |
| Locomotion24DofStraightKnee (813), Locomotion24DofWalk (814), AmpMotion (812) | Damping |
| LocomotionGait, LocomotionArmsdk (831), ArmSdkLocomotion (816) | Damping, Stance |
| LocomotionArmsdkGait | Damping, Stance, AmpLocomotion |
Every locomotion state additionally black-lists `ZeroTorque` and `Keep`.
**Consequence: 804 / 805 are reachable only from a state with an empty white
list — in practice `Stance` (4, the app's "Lock").** You cannot enter a mimic
state from Damping, ZeroTorque, Keep, or from **Run (811)**. The same is true of
813 / 814: Lock → 813 works, Run → 813 does not.
- `armsdk` control is only accepted in **816**, **831** or **833**.
- The universal escape hatch is `Damping` (`{"data":1}`), allowed from every
state above.
A refused `SetFsmId` answers with **api error code 1001** — the literal
`SetFsmIdHandler::handle` stores when `StateMachine::changeState()` fails. So
from 813 (Walk Straight Knee) a request for Lock returns `code=1001`, and the
only way out is **Damping first, then Lock**. The official Explorer app gets the
same 1001; this is enforced entirely on the robot.
### 2. `white_list.yaml` — what gates *reporting* only
```yaml
white_list: ["ZeroTorque", "Damping", "Stance", "AmpMotion22Dof", "AmpMotion",
"Locomotion20Dofs", "LocomotionGait", "LocomotionArmsdkGait", "AmpLocomotion",
"ArmSdkLocomotion", "LocomotionArmsdk", "Qishen", "Tangxia",
"dance1_subject2_segment1", "dance1_subject2_segment2",
"dance1_subject2_segment3", "niuniuwu", "gongfu", "jiequandao"]
```
`LocoServer::SendSportMode()` and `SendSportModeLF()` publish the state **only
while the current state's name is in this list**, and `GetFsmId` (7001) refuses
for the same reason (*"FSM state name '…' not in white list, deny returning
fsm_id"*). `SetFsmId` (7101) does **not** consult it at all: it parses `data`
and hands it straight to the state machine.
So this list does not stop you entering anything. What it does is make the
robot **go silent**: in a non-listed state `rt/lf/sportmodestate` stops being
published entirely and `fsm_id` freezes at whatever was last reported. The
states this affects — see `R1_UNREPORTED_FSM` in `action-bar.ts` — are
**5, 6, 7, 800, 804, 805, 813, 814**. Any client tracking the mode has to adopt
those optimistically on send and treat the next report as "we've left".
### Live motion set on current firmware
There is **no `Twist` motion**. The dance set is smaller than the id table
suggests — **601 and 605 are commented out** in both `dances.yaml` and
`dances_air.yaml`, so they don't exist as states:
| Config | Live dance ids |
|---|---|
| `dances.yaml` (EDU) | 602, 603, 604, 607, 608 |
| `dances_air.yaml` (**AIR**) | **602, 604, 607** |
Two states also exist that predate no earlier notes: **`LocomotionGait`** and
**`LocomotionArmsdkGait`** (`LocomotionArmsdkGaitCore`, id **833** per the
`armsdk` list in `white_list.yaml`; `LocomotionGait`'s id isn't stated in any
config — 832 by adjacency, unverified). Neither class exists on older
firmware, so they are new.
## Speed profile (R2 + UP / DOWN)
### There are exactly two
`speed_profile_` is an `int` that only ever holds **0 (low)** or **1 (high)** —
no ladder, no levels in between. `AmpMotionCore::handleSpeedAdjustmentInput()`
runs once per control tick from `update()` and does:
```cpp
if (!use_legacy_speed_adjustment_) { // the shipping path
if (isKeyHold(0x10 /*R2*/) && isKeyPressed(0x1000 /*UP*/) && speed_profile_ != 1)
{ speed_profile_ = 1; resetSpeedLimits(); LOG("speed_profile: high, vx_forward_max=" << max_cmd_[0]); }
if (isKeyHold(0x10 /*R2*/) && isKeyPressed(0x4000 /*DOWN*/) && speed_profile_ != 0)
{ speed_profile_ = 0; resetSpeedLimits(); LOG("speed_profile: low, vx_forward_max=" << max_cmd_[0]); }
}
```
and `resetSpeedLimits()` re-derives the limit from scratch:
```cpp
max_cmd_ = init_max_cmd_; // [1.0, 0.6, 1.0]
if (!use_legacy_ && speed_profile_ == 1) max_cmd_[0] = high_speed_vx_; // 2.5 m/s
max_cmd_[0] = std::min(max_cmd_[0], temperature_vx_limit_); // 5.0 normal / 1.0 hot
cmd_limit_ = max_cmd_;
```
So on Run (811, `AmpMotion22Dof`):
| profile | forward limit | source |
|---|---|---|
| low (default) | **1.0 m/s** | `init_max_cmd[0]` in `amp_motion_22dof.yaml` |
| high | **2.5 m/s** | hardcoded in the `AmpMotionCore` constructor |
`normal_temperature_speed_limits: 5.0` / `high_temperature_speed_limits: 1.0`
is the cap the `std::min` applies — when a winding crosses
`winding_high_temperature` (130 °C) or a driver crosses 85 °C, high collapses
onto 1.0 and the two profiles become identical. Lateral and yaw limits (0.6 /
1.0) never change; only vx does.
`AmpMotionCore::enter()` writes `speed_profile_ = 0` and calls
`resetSpeedLimits()`, so **every entry into Run starts on low**.
`AmpLocomotion` carries the same two-profile code with its own numbers —
`low_speed_forward_vx_limit: 0.9` / `high_speed_forward_vx_limit: 2.3` in
`amp_locomotion.yaml`. `Locomotion20Dofs` (830, the AIR redirect target) has no
speed handler at all: fixed `default_vx_forward_limit: 1.0`.
### Legacy mode (built but switched off)
`setUseLegacySpeedAdjustment(bool)` selects an older incremental scheme, and the
flag is initialised to **false** in the constructor with no caller anywhere in
the controller — it is dead in shipping firmware. For reference, it is a ±step
ladder rather than a toggle, on different keys:
| combo | effect |
|---|---|
| R1 hold + UP / DOWN | `max_cmd[0] ± 0.5`, floor 0, logs `x_cmd_max_gamepad:` |
| R1 hold + RIGHT / LEFT | `max_cmd[1] ± 0.25`, floor 0, logs `y_cmd_max_gamepad:` |
| R2 hold + UP / DOWN | `max_cmd[2] ± 0.5`, floor 0, logs `yaw_cmd_max_gamepad:` |
There is no upper clamp on the legacy ladder other than the temperature `min()`.
### It is a command clamp, not a policy input
The profile never reaches the network. `buildVelocityCommand()` multiplies the
normalised joystick axis by `cmd_limit_[i]`, and that product is what lands in
the observation vector. `amp_motion_22dof.yaml` spells the layout out:
```
# 3 + 3 + 22 + 22 + 22 = 72
num_obs: 72 # ang_vel(3) + cmd vx/vy/wz(3) + dof_pos(22) + dof_vel(22) + last_action(22)
memory_size: 5
```
Three command floats, no discrete speed/gait channel — the policy only ever sees
"go 0.8 m/s forward", never "you are in high mode". And the three motion model
slots collapse onto one file, so there is not even a net swap:
```yaml
walk_slow_model: ".../20250915_15-25-57__58600/actor.onnx" ┐
walk_fast_model: ".../20250915_15-25-57__58600/actor.onnx" ├─ same weights
run_model: ".../20250915_15-25-57__58600/actor.onnx" ┘
stance_model: ".../20250919_16-11-28__68000/actor.onnx" ← the only other net
```
Ramping is per-tick on the command, not instant: `vx_acc_up_delta: 0.02` /
`vx_acc_down_delta: 0.05` at 50 Hz, i.e. ~1 m/s of extra top speed takes about a
second to build.
(`AmpLocomotion` is the exception to the "no net swap" rule — its config gates
`run_model` on `vx: [1.5, 100]` and `turn_model` on `|wz| ≥ 1.0` with
`|vx|,|vy| ≤ 0.3`, so crossing 1.5 m/s there really does switch networks. That
still keys off the command value, not off `speed_profile_`.)
### The profile is unreachable from a client
Two separate walls, either one sufficient:
**No api.** `ROBOT_API_ID_LOCO_SET_SPEED_MODE = 7107` exists as a constant, but
`LocoServer::Init` binds only 7001 / 7101 / 7105 / 7108 — the sole 7107 reference
in the controller is inside `G1ArmActionClient::Init()`.
**No key channel either.** Replaying the R2 + UP gesture on
`rt/wirelesscontroller` cannot work: the loco service's subscriber for that topic
reads **only the four axes** —
```cpp
// Bridge ctor, WirelessController_ subscriber
gamepad.setUpWirelessControllerData(msg.lx(), msg.ly(), msg.rx(), msg.ry());
```
— and `WirelessController_::keys()` is never called by application code at all
(its only references are inside the CycloneDDS serializer templates). Buttons
reach the `Gamepad` state machine through exactly one path: the LowState
subscriber memcpy's `LowState_.wireless_remote` (40 bytes, straight from the
physical remote) into the gamepad buffer and calls `Gamepad::update()`. Nothing
a WebRTC client publishes can put a key into that buffer.
### What works instead: SetVelocity (7105) ignores the limit
`AmpMotionCore::controllerTask()`:
```cpp
target = buildVelocityCommand(gamepad_axes); // axes .* cmd_limit_ ← profile applies here
api_duration_ -= dt;
if (api_duration_ > 0 && target.norm() < 0.1) // only while the sticks are released
target = api_velocity_; // used RAW — never multiplied by cmd_limit_
```
`api_velocity_` and `api_duration_` are written by the `SetVelocity` handler, and
nothing downstream clamps them back to `cmd_limit_`, so 7105 commands whatever vx
it asks for regardless of the profile. Two conditions: the joystick axes must be
near zero (a client using this must stop publishing sticks), and `api_duration_`
decays by `dt` per control step, so the request has to be repeated. The normal
`vx_acc_up_delta` ramp still applies, so speed builds over about a second rather
than stepping.
**And there is no readback.** R1 publishes `unitree_hg::msg::dds_::SportModeState_`,
a 16-byte struct with exactly four members — `fsm_id_`, `fsm_mode_`, `task_id_`,
`task_time_` — and `Bridge::SendSportMode{,LF}()` writes **only `fsm_id`** before
`Write()`, so the other three are always 0. There is no `speed_level` field at all
(the webview's `speed_level: n.speed_level ?? 0` is inherited from the Go2
message shape and always reads 0 here). The full loco api set for reference:
| api_id | name |
|---:|---|
| 7001 | `GET_FSM_ID` ✅ served |
| 7002 | `GET_FSM_MODE` — not bound |
| 7007 | `GET_ARM_SDK_STATUS` — not bound |
| 7101 | `SET_FSM_ID` ✅ served |
| 7105 | `SET_VELOCITY` ✅ served |
| 7106 | `SET_ARM_TASK` (arm topic) |
| 7107 | `SET_SPEED_MODE` — **not bound** |
| 7108 | `SET_PUNCH_API` ✅ served (this is SetMotion) |
| 7109 | `SET_ARM_SDK_STATUS` — not bound |
## Remote-control bindings
Transitions are declared per state as `state_change_map` entries of the form
`- TargetState: ["<key>_<event>", …]`, where the event is one of
`pressed / released / hold / idle / double / long (2 s) / long5s (5 s)`.
| From | Combo | To |
|---|---|---|
| any locomotion (811-816, 830, 831) | **L2 hold + B held 5 s** | Damping |
| any locomotion | **L2 hold + UP** | Stance (Lock) |
| 811 / 815 / 830 / `LocomotionGait` | **L2 hold + X long** | Tangxia (lie down) |
| 811 / 815 / 830 / `LocomotionGait` | **L2 hold + LEFT long** | SitDown |
| dances (601-608) | **L2 hold + B long** (2 s) *or* **START double-click** | Damping |
| **804 / 805**, Qishen, Tangxia, SitDown | **L2 hold + B** (instant) | Damping |
| Stance (Lock) | **R2 hold + A** | AmpLocomotion |
| Stance | **R2 hold + X** | LocomotionGait |
| Stance | **R2 hold + Y** | LocomotionArmsdkGait |
| Stance | **L2 hold + X long** | Qishen (stand up from ground) |
| Damping | **L2 hold + Y** | ZeroTorque |
| Damping / ZeroTorque | **L2 hold + UP** | Stance |
| Damping / ZeroTorque | **START double-click** | Keep |
### There is no remote combo for 804 / 805
The bindings exist in the config but are **commented out**, in both
`fsm/motion.yaml` and `fsm/statics/stance.yaml`:
```yaml
# - MimicPunch: ["R1_hold", "A_pressed"]
# - MimicActor: ["R1_hold", "B_pressed"]
```
So R1+A / R1+B were the intended combos and are dead on this firmware — the
mimic states are reachable **only** through `SetFsmId` (7101). Once inside,
the remote does work for clip selection (`motion_button_map`), and L2+B still
gets you out.
## Stopping, interrupting, and getting up after a fall
Three different things, often confused:
| Want | Remote | API |
|---|---|---|
| **E-stop** (motors compliant, robot flops) | **L2 + B** — instant in mimic/dance-adjacent states, **5 s hold in locomotion** | `SetFsmId {"data":1}` (Damping) |
| **Motors fully free** | **L2 + Y** from Damping | `SetFsmId {"data":0}` (ZeroTorque) |
| **Cut a running mimic clip short** | **START double-click** | *(none — see below)* |
| **Drop queued clips** | — | `SetMotion {"data":[0,1]}` |
| **Stand up after a fall** | **L2 + X long** from Stance | `SetFsmId {"data":701}` (Qishen) |
Notes that matter in practice:
- **Damping is the e-stop.** It's white-listed, so `{"data":1}` is accepted from
every state including 804/805 — it's the one command that always lands. The
robot goes limp, so expect it to drop where it stands.
- **The 5-second hold is real.** In locomotion states the config asks for
`B_long5s`, which is `KeyState 6` at a 5000 ms threshold. Only the mimic,
recovery and sit states use the instant `B_pressed`.
- **Clips can't be interrupted by the API.** `SetMotion` with terminator `1`
clears the *queue* only; the running clip plays to its last frame because
both the button path and the queue path are gated on
`frame_counter >= total_frames`. The single exception is the START
double-click abort at the top of `MimicCore::update()`, which force-sets the
frame counter past the end and clears the queue. That's a clip abort, not a
stop — the robot stays in the mimic state on its default clip.
- **Falls are detected, not auto-recovered.** `MimicCore` tracks a
`FallDownState` and, for 805, has `front_get_up_traj_name` /
`back_get_up_traj_name` wired to `get_up` (remote: L2+UP) — 804 leaves all
four get-up/get-down names **empty**, so the actor state has no self-recovery
at all. From a fall, the reliable sequence is Damping → Stance → Qishen (701),
which carries both face-up and face-down policies.
## Triggering actions *within* a state (804 MimicActor / 805 MimicPunch)
> ⚠️ **Removed on current firmware.** The state names `MimicActor` and
> `MimicPunch` no longer exist — `SetFsmId {"data":805}` answers
> **`code=1003`** ("invalid fsm id"), i.e. `findState()` found nothing. They
> were present on older firmware, which is what the material below describes.
> Current firmware still ships `mimic_{actor,punch}.yaml`, all the
> `trajs/mimic_*` CSVs and the ONNX policies — leftovers with no state to run
> them. Same story for `SimpleMotion`; conversely `LocomotionGait` and
> `LocomotionArmsdkGait` are new. So the material below is reference for the sim
> (the policies load fine in mujoco), not something the robot will execute.
804 and 805 are **not single motions** — each is one policy that plays a *library
of clips*. `SetFsmId` (7101) only **enters** the state; **which clip plays is a
second selection**. Two independent channels do that selection:
| Channel | Transport | Works for | How |
|---|---|---|---|
| **Remote / gamepad** | `rt/wirelesscontroller` (button bits) | 804 **and** 805 | button gesture → `motion_button_map` |
| **SetMotion API** | `rt/api/sport/request`, api_id **7108** | **805 only** | `{"data": [<id>…, 0]}` |
The catch: only **805 MimicPunch** clips carry an `api_id`, so only they are
reachable by the `SetMotion` API. **804 MimicActor** clips have **no `api_id`**
(`offline_trajs: [50, 0, 0]`), so they're reachable **only** from the physical
remote (there is no per-clip API for the actor, and button state cannot be
injected over `rt/wirelesscontroller` — see Channel 1).
### How the controller actually picks a clip
Both states are the same C++ class, `ucontrol::state::MimicCore` — they differ
only by the config file (`mimic_name: actor | punch`). `MimicCore::update()`
runs once per 50 Hz tick and does, in order:
1. **Abort** — a double-click of **START** (`0x0004`) force-finishes the running
clip and clears the api queue.
2. **Button-map scan** — walk `motion_button_map`, first match wins.
3. **API queue** — pop the front of a `std::deque<int>` fed by `SetMotion`, look
the id up in the `api_id → traj name` map built from `offline_trajs`.
Both 2 and 3 are gated on the same idle test —
`frame_counter == 0 || frame_counter >= total_frames_of_current_clip` — so **a
clip can never be interrupted**, only queued behind. Whichever wins calls
`set_motion_api(name)`, which also arms that clip's cooldown
(`offline_trajs[2] / dt`); the button path refuses while the cooldown is live
and logs `Motion: <name>, cooling time remain: <n>` (every current punch clip
has `cool_time: 0`, so this is dormant today).
### The button gesture is not a plain press
The match predicate reads the on-robot `Gamepad` key states, not raw bits:
| `motion_button_map` entry | Required key state |
|---|---|
| single key — `["button_X_"]` | **`DOUBLE_CLICK`** — two presses ≤ **500 ms** apart |
| combo — `["button_UP_", "button_R1_"]` | **first** key `PRESSED` (a fresh edge) **+ every other** key `HOLD` |
`KeyState` is `0 IDLE, 1 HOLD, 2 PRESSED, 3 RELEASED, 4 DOUBLE_CLICK,
5 LONG_PRESS (2 s), 6 LONG_PRESS_5S (5 s)`. `Gamepad::update()` advances each
key one step per call, but only once the raw bit has been identical across its
**3-sample history** (a debounce), and it is called both from the main loop and
from every LowState message. Holding a bit therefore does **nothing** for a
single-key clip (which wants `DOUBLE_CLICK`), and for a combo the modifier must
already have reached `HOLD` before the trigger's fresh edge.
### 804 · MimicActor — 3 performances, **button-only**
| Button | Clip |
|---|---|
| **X** | `vq_actor_Take_111` |
| **B** | `vq_actor_Take_125` |
| **A** | `vq_actor_Take_142` |
| *(none)* | `vq_default` — idle/rest embedding (stands still) |
No `SetMotion` path — press the button (or emulate it, see Channel 1 below).
### 805 · MimicPunch — ~20 moves, **button + api_id**
| Buttons | Move (meaning) | SetMotion `api_id` |
|---|---|---:|
| **X** | 057 — **defense** (防御) | **64** |
| **A** | 093 | 81 |
| **B** | 090 | 80 |
| **Y** | 094 — ready (预备) | 82 |
| **UP** | 035 — turn in place (原地掉头) | 60 |
| **LEFT / RIGHT / DOWN** | 051 variants | 61 / 62 / 63 |
| **UP+R1 / LEFT+R1 / RIGHT+R1 / DOWN+R1** | 007 hand variants | 0 / 1 / 2 / 3 |
| **UP+R2 / LEFT+R2 / RIGHT+R2** | 018 leg variants | 20 / 21 / 22 |
| **DOWN+R2** | 020 | 23 |
| **SELECT+R1** | 026 — big hand | 40 |
| **SELECT+R2** | 042 — big hand | 41 |
| **DOWN+L2** | 061 — get-up / get-down | 201 |
| **UP+L2** | get-up (internal) | *(none)* |
| **LEFT+L2** | 067 | *(none)* |
### Channel 1 — the physical remote (both states, no API)
The controller reads `motion_button_map` and plays the clip whose button
gesture the `Gamepad` state machine just observed. `keys` is a `uint16`
bitfield (from the `unitree_sdk2` gamepad struct, LSB first):
```
bit 0 R1 bit 4 R2 bit 8 A bit 12 up
bit 1 L1 bit 5 L2 bit 9 B bit 13 right
bit 2 start bit 6 F1 bit 10 X bit 14 down
bit 3 select bit 7 F2 bit 11 Y bit 15 left
```
Examples: defense = `X` = `1<<10` = `0x0400`; big hand = `SELECT+R1` =
`(1<<3)|(1<<0)` = `0x0009`; get-up = `L2+UP` = `(1<<5)|(1<<12)` = `0x1020`.
> ⚠️ **This channel is not reachable from a client.** Publishing a
> `unitree_go::msg::dds_::WirelessController_` on `rt/wirelesscontroller` with
> `keys` set does nothing: the loco service's subscriber for that topic reads
> only the four axes —
>
> ```cpp
> gamepad.setUpWirelessControllerData(msg.lx(), msg.ly(), msg.rx(), msg.ry());
> ```
>
> — and `WirelessController_::keys()` is never called by application code
> (its only references are inside the CycloneDDS serializer templates). Buttons
> enter the `Gamepad` through exactly one path: the LowState subscriber memcpy's
> `LowState_.wireless_remote` (40 bytes, straight from the physical remote) into
> the gamepad buffer and calls `Gamepad::update()`. The axes are therefore
> drivable over WebRTC and the buttons are not — which is why every
> button-gated behaviour in this document (clip selection, the speed profile,
> the `state_change_map` transitions) is remote-only.
### Channel 2 — SetMotion API (805 only)
`LocoServer::SetMotion` parses `data` into a `std::vector<float>` and reads the
**last element as a terminator**, so the parameter is always an array of ≥2
entries — a scalar `{"data": 64}` is rejected with *"At least two values are
required, end value (0/1) !"*.
| Terminator | Effect |
|---|---|
| `0` | append every preceding id to the clip queue |
| `1` | clear the queue and drop the pending clip |
```
1. SetFsmId : api_id 7101, param {"data": 805}
2. SetMotion: api_id 7108, param {"data": [64, 0]} # queue defense
SetMotion: api_id 7108, param {"data": [82, 64, 80, 0]} # queue a 3-move playlist
SetMotion: api_id 7108, param {"data": [0, 1]} # flush the queue
```
Ids are clamped on-robot to `[0, 1000]`; an id missing from `offline_trajs` logs
`punch api: <n> not found !` and is skipped. There is **no query API** for the
live id set — the `Get motion ids:` log line is just the controller echoing back
what you queued.
## In this app
The app mirrors the official R1 webview: **`R1_MODES` exposes the four modes the
webview does** — Damping (1), Zero Torque (0), Lock (4), Run (811), in that
order — plus the lie ↔ stand pair (701 / 702) and the dance / martial-arts set
(601-604, 607, 608, where 604 `niuniuwu` 扭扭舞 is labelled **Twist**), and Sit
Down (7).
`R1_ACTIONS` is the 13 arm gestures on api 7106. Nothing else is exposed: the
rest of the FSM table above is documentation, not buttons.
Behaviour copied from the webview:
- **Grey-out** follows `r1RowDisabled`, transcribed from the webview's
`OperaBar` template (the `menu-item` class binding appends `"disable"` per
condition). Only three of those clauses are guarded by `series == "R1"`:
| live state | greyed |
|---|---|
| Damping | **Run** |
| Zero Torque | **Run** |
| Damping / Zero Torque / Lock | **all arm gestures** |
Every other clause in that binding is `series == "G1"` and must not be applied
to R1 — doing so is what used to grey Lock and Zero Torque while in Run. The
webview greys nothing from the full per-state transition table, which is why
Lock stays live in 813 and the FSM answers 1001 instead; we match that.
The webview additionally disables every row while a command is in flight,
while the joystick is moving, or while the e-stop is engaged; we cover the
e-stop case through the existing lockout.
- **`r1ClickGuard` (app.ts)** reproduces the webview's two click-time rules:
Zero Torque is refused unless the robot is already damping (the webview's
`toastMsg_29`), and Run is a no-op while already running. Everything else is
sent, and the FSM has the final say.
- **`r1ModeToState`** is a transcription of the webview's `Yo` mode map, fed the
same way the webview feeds it: `mode ?? fsm_id`. 3 → ZeroTorque, and Run
covers 811 / 816 / 830 / 831 because an AIR robot redirects a request for 811
to 830 on-robot.
- **Error codes are decoded** (`R1_FSM_ERRORS`): a failed 7101 logs and toasts
what 1001 / 1002 / 1003 actually mean instead of a bare number.
The e-stop sends `7101 {"data":1}` (Damping) with `priority: true`. Damping
appears in every state's `set_fsm_white_list`, so it is the one transition that
is always accepted — including from states that refuse everything else.

View File

@@ -0,0 +1,236 @@
# BLE Remote Control
The Unitree BLE remote control (e.g. `Unitree-32KC0D`) is a separate device from the robot — a handheld gamepad that streams stick + button state over BLE. The Unitree mobile app pairs with both the remote and the robot, then forwards remote inputs to the robot over WebRTC. This document covers both halves of that path:
1. [Connecting to the remote over BLE](#ble-connection) — name prefix, GATT service, MTU, handshake, notification format, button mapping.
2. [Forwarding inputs to the robot over WebRTC](#webrtc-relay) — DataChannel topic, message format, keys bitmask, RSSI thresholds.
The remote is independent from the robot's BLE provisioning flow: BlueZ setup, adapter selection, and scanning are shared infrastructure documented in [bluetooth-v1-v2.md](bluetooth-v1-v2.md).
## Compatibility
Tested with the BLE remote bundled with Unitree Go2; the same protocol applies to BLE remotes paired with G1 (the remote hardware is identical, only the host robot differs).
## Table of Contents
- [BLE Connection](#ble-connection)
- [Discovery](#discovery)
- [Connection Challenges](#connection-challenges)
- [Connection Flow](#connection-flow)
- [Handshake](#handshake)
- [Notification Packet (20 bytes)](#notification-packet-20-bytes)
- [Button Mapping](#button-mapping)
- [Physical Layout](#physical-layout)
- [WebRTC Relay](#webrtc-relay)
- [Data Flow](#data-flow)
- [WebRTC Message Format](#webrtc-message-format)
- [Keys Field](#keys-field)
- [Input Sources](#input-sources)
- [RSSI Signal Thresholds](#rssi-signal-thresholds)
- [Quick Reference](#quick-reference)
---
## BLE Connection
### Discovery
The remote advertises with a name beginning `Unitree-` (e.g. `Unitree-32KC0D`). It exposes the V1 (FFE0) GATT service — the same UUIDs the robot uses, but with a different handshake and data format. Scanning logic that filters for robots by `Go2_` / `G1_` prefixes must explicitly include `Unitree-` to find the remote.
| Service / characteristic | UUID |
|---|---|
| Service | `0000ffe0-0000-1000-8000-00805f9b34fb` |
| Notify (remote → client) | `0000ffe1-0000-1000-8000-00805f9b34fb` |
| Write (client → remote) | `0000ffe2-0000-1000-8000-00805f9b34fb` |
### Connection Challenges
The remote is a **dual-mode** Bluetooth device (classic BR/EDR + BLE) and advertises with a **public** Bluetooth address. BlueZ's D-Bus API (`Device1.Connect()`) defaults to classic Bluetooth for such devices, which fails with `br-connection-profile-unavailable` because the remote does not expose a classic profile.
**Solution:** Use `gatttool` (via `pygatt`) which forces BLE/LE transport directly, bypassing BlueZ's transport auto-selection.
### Connection Flow
```
1. BLE Scan -> Find device with name starting with "Unitree" (not Go2_/G1_)
2. LE Connect -> Force BLE transport (gatttool -t public)
3. Set MTU -> Request MTU 64 (200ms after connect)
4. Subscribe -> Enable notifications on FFE1
5. Handshake -> Write hex-encoded "YS+2" to FFE2
```
### Handshake
The handshake string `"YS+2"` is converted to its hex-character representation:
```
'Y' = 0x59 -> "59"
'S' = 0x53 -> "53"
'+' = 0x2B -> "2b"
'2' = 0x32 -> "32"
Result: b"59532b32" (8 ASCII bytes written to FFE2)
```
This is **not** AES-encrypted — it is sent as raw bytes. Note this differs from the robot handshake (`"unitree"`, AES-encrypted, opcode `0x01`); the remote uses neither encryption nor the V1/V2 framing.
### Notification Packet (20 bytes)
After handshake, the remote streams 20-byte packets at ~20 Hz on the notify characteristic (FFE1):
```
Offset Size Type Field
────── ──── ────────── ─────────────────
0 4 float32 LE Left Stick X (lx)
4 4 float32 LE Right Stick X (rx)
8 4 float32 LE Right Stick Y (ry)
12 4 float32 LE Left Stick Y (ly)
16 1 uint8 Button byte 1
17 1 uint8 Button byte 2
18 1 uint8 Battery (0-100%)
19 1 uint8 RSSI
```
Joystick values are IEEE 754 floats, range approximately -1.0 to 1.0.
### Button Mapping
**Byte 16 — Shoulder & Function:**
| Bit | Button |
|-----|--------|
| 0 | R1 |
| 1 | L1 |
| 2 | Start |
| 3 | Select |
| 4 | R2 |
| 5 | L2 |
| 6 | F1 |
| 7 | F2 |
**Byte 17 — Face & D-Pad:**
| Bit | Button |
|-----|--------|
| 0 | A |
| 1 | B |
| 2 | X |
| 3 | Y |
| 4 | Up |
| 5 | Right |
| 6 | Down |
| 7 | Left |
Check if a button is pressed:
```python
pressed = bool((byte >> bit) & 1)
```
### Physical Layout
```
[L2] [L1] [R1] [R2]
( Left Stick ) ( Right Stick )
[Up] [Y]
[Left] [Right] [X] [B]
[Down] [A]
[F1] [Select] [F2] [Start]
```
---
## WebRTC Relay
The Unitree mobile app relays remote-control BLE data to the robot over WebRTC. This is how the remote actually drives the robot when both are paired through the phone — there is no direct BLE link between remote and robot.
### Data Flow
```
BLE Remote (20-byte notification)
-> Android BleNotifyCallback
-> EventBus: AppSendRockerEvent(comma_separated_bytes)
-> WebRTCFragment.onMessageEvent()
-> evaluateJavascript("appSendRocker", raw_byte_string)
-> JS dealRocker() parses bytes
-> publish("rt/wirelesscontroller", {lx, ly, rx, ry, keys})
-> WebRTC DataChannel
-> Robot
```
### WebRTC Message Format
```json
{
"type": "msg",
"topic": "rt/wirelesscontroller",
"data": {
"lx": 0.0,
"ly": 0.0,
"rx": 0.0,
"ry": 0.0,
"keys": 0
}
}
```
### Keys Field
The `keys` field is a uint16 bitmask packing all 16 buttons in order:
```
Bit 0: R1 Bit 8: A
Bit 1: L1 Bit 9: B
Bit 2: Start Bit 10: X
Bit 3: Select Bit 11: Y
Bit 4: R2 Bit 12: Up
Bit 5: L2 Bit 13: Right
Bit 6: F1 Bit 14: Down
Bit 7: F2 Bit 15: Left
```
### Input Sources
The same `rt/wirelesscontroller` topic is used by three input sources:
| Source | Bridge Method | Notes |
|---|---|---|
| BLE Remote | `appSendRocker(bytes)` | Raw 20-byte notification forwarded |
| USB/Android Gamepad | `appSendJoystick(json)` | ly and ry are **negated** |
| Virtual Joystick (on-screen) | Direct JS publish | No `keys` field (buttons not applicable) |
### RSSI Signal Thresholds
The remote-status UI in the Unitree app maps the RSSI byte (offset 19 of the BLE notification) to a 0–5 bar indicator:
| RSSI (dBm) | Signal Level |
|---|---|
| >= -70 | Excellent |
| >= -75 | Good |
| >= -83 | Fair |
| >= -90 | Weak |
| < -100 | Very weak |
---
## Quick Reference
| Constant | Value |
|---|---|
| Name prefix | `Unitree-` |
| Service UUID | `0000ffe0-0000-1000-8000-00805f9b34fb` |
| Notify char | `0000ffe1-0000-1000-8000-00805f9b34fb` |
| Write char | `0000ffe2-0000-1000-8000-00805f9b34fb` |
| Address type | Public |
| Transport | LE only (force via `gatttool -t public`) |
| MTU request | 64 (200 ms after connect) |
| Handshake bytes | `"59532b32"` (hex of `"YS+2"`) |
| Handshake encryption | None |
| Notification size | 20 bytes |
| Notification rate | ~20 Hz |
| WebRTC topic | `rt/wirelesscontroller` |
| Keys bitmask | uint16, bits 0–15 = R1,L1,Start,Select,R2,L2,F1,F2,A,B,X,Y,Up,Right,Down,Left |
For the robot-side BLE protocol (provisioning, WiFi, serial number), see [bluetooth-v1-v2.md](bluetooth-v1-v2.md). For the V3 extension on G1 ≥ 1.5.1 / Go2 ≥ 1.1.15, see [bluetooth-v3.md](bluetooth-v3.md).

View File

@@ -0,0 +1,21 @@
# Service Manager
View and control the on-board services running on the robot's MCF service bus. Useful for debugging, recovering from a hung service, or temporarily disabling something the firmware fights you over (obstacle avoidance, charge triggers, etc.).
<p align="center">
<img src="../images/services_go2.png" width="48%" />
<img src="../images/services_g1.png" width="48%" />
</p>
## What it Shows
Each row is one MCF service:
- **Name** — service identifier as the robot publishes it.
- **Status** — running / stopped / protected.
- **Protect** — services flagged as "protected" by firmware will be auto-restarted if you stop them. The UI surfaces the flag so you don't get blindsided.
- **Actions** — Start / Stop buttons. Protected services will prompt before allowing stop.
## Family Differences
Go2 and G1 expose overlapping but not identical service trees. The UI subscribes to whichever topic the connected firmware advertises and renders the rows it gets back — there's no hardcoded service list per family.

View File

@@ -0,0 +1,329 @@
# 3D LiDAR Mapping (SLAM) — Unitree Go2
The Mapping page in this UI talks to the robot's `uslam_server` (the on-board SLAM module) over WebRTC and lets the user build maps, localize on saved maps, navigate to a goal, run a patrol loop, and dock for auto-charging — all from the browser. This document covers the topics involved, the user-facing flow, the local data model, and the firmware-side state machine.
![SLAM view](../images/slam.png)
## Table of Contents
- [Overview](#overview)
- [Page Layout](#page-layout)
- [Topics](#topics)
- [Command channel](#command-channel)
- [Status feeds](#status-feeds)
- [File transfer](#file-transfer)
- [Shared topics](#shared-topics)
- [User Flow (Stepper)](#user-flow-stepper)
- [Mapping](#mapping)
- [Localization](#localization)
- [Navigation](#navigation)
- [Go to Goal](#go-to-goal)
- [Patrol](#patrol)
- [Patrol Limits](#patrol-limits)
- [Auto-Charge](#auto-charge)
- [Map Storage](#map-storage)
- [Why a local cache](#why-a-local-cache)
- [IndexedDB layout](#indexeddb-layout)
- [Zip Import / Export](#zip-import--export)
- [Status Queries](#status-queries)
- [Theme + Smoothing](#theme--smoothing)
- [Known Quirks](#known-quirks)
- [Files](#files)
---
## Overview
The SLAM stack on the Go2 is exposed by **`uslam_server`** (ARM64 ELF at `/unitree/module/unitree_lidar_slam/bin/uslam_server`). It speaks DDS internally and the WebRTC bridge surfaces those topics to the browser. All command traffic is **plain string** publications on a single topic; data feeds (point clouds, odometry, paths) come back on per-module topics. There is no API-ID protocol here — just `module/action[/arg1/arg2/...]` strings.
The on-board module exposes ~56 commands across 8 namespaces (`mapping`, `localization`, `navigation`, `patrol`, `autocharge`, `frontend`, `control`, `common`). This UI drives the user-facing subset (the same one the official Unitree app uses).
## Page Layout
| Region | Content |
|---|---|
| Header | back button, title, motor temp, battery, network type, theme, BT — same shape as the Control NavBar |
| Stepper banner | `Step 1: Mapping ➜ Step 2: Localization ➜ Step 3: Navigation` with green/yellow/grey state |
| Live detail bar | Compact line under the stepper showing nav/patrol/charge sub-state |
| Left sidebar | Send Command (template picker), Server Log (with source-topic indicator + Copy) |
| 3D viewport | Three.js scene + draggable PiP video feed |
| Right sidebar | Step 1: Mapping, Step 2: Localization, Step 3: Navigation (Goal/Patrol tabs), Autocharge |
## Topics
Constants are defined in [src/protocol/topics.ts](../src/protocol/topics.ts).
### Command channel
| Topic | Direction | Format |
|---|---|---|
| `rt/uslam/client_command` (`USLAM_CMD`) | client → robot | string `module/action[/arg.../arg]` |
Every SLAM command we send goes on this topic. The robot acknowledges via paired success/failed messages on the server-log topic (e.g. `mapping/start/success`, `localization/set_initial_pose/failed`).
### Status feeds
All received over the WebRTC DataChannel.
| Topic | Constant | Payload | Purpose |
|---|---|---|---|
| `rt/uslam/server_log` | `USLAM_SERVER_LOG` | std_msgs/String | Command acks, state transitions, log lines |
| `rt/uslam/frontend/cloud_world_ds` | `USLAM_CLOUD_WORLD` | binary, voxel-compressed | Live downsampled world cloud (during mapping) |
| `rt/uslam/frontend/odom` | `USLAM_ODOM` | nav_msgs/Odometry | Mapping-frame odometry |
| `rt/uslam/cloud_map` | `USLAM_CLOUD_MAP` | binary (PCD) | Final map cloud (delivered post-stop) |
| `rt/uslam/localization/cloud_world` | `USLAM_LOC_CLOUD` | sensor_msgs/PointCloud2 | Real-time localization scan in world frame |
| `rt/uslam/localization/odom` | `USLAM_LOC_ODOM` | nav_msgs/Odometry | Robot pose during localization |
| `rt/uslam/navigation/global_path` | `USLAM_NAV_PATH` | sensor_msgs/PointCloud2 | Planned global path |
| `rt/mapping/grid_map` | `USLAM_GRID_MAP` | binary | 2-D occupancy grid (subscribed but not currently rendered) |
Subscription strategy (matches the APK's deferred-subscription pattern):
- **On page entry** — subscribe BASE topics: `USLAM_SERVER_LOG`, `USLAM_CLOUD_WORLD`, `USLAM_ODOM`, `USLAM_CLOUD_MAP`, `USLAM_GRID_MAP`.
- **On `[Localization] initialization succeed!`** — additionally subscribe LOC topics: `USLAM_LOC_CLOUD`, `USLAM_LOC_ODOM`, `USLAM_NAV_PATH`.
- **On localization stop / failure / page leave** — unsubscribe LOC topics.
- **On page leave** — unsubscribe BASE topics; **`LOW_STATE` is intentionally not in this list** (it's globally subscribed by `app.ts:enableVideoAndSubscribe()` and shared with the NavBar).
### File transfer
Map artefacts (`.pcd`, `.pgm`, `.txt`) move over the **`RTC_INNER_REQ`** WebRTC subprotocol with `related_bussiness: "uslam_final_pcd"`. Two ops:
| Op | `req_type` | Direction |
|---|---|---|
| Read a file | `request_static_file` | client → robot, response carries chunked base64 |
| Write a file | `push_static_file` | client → robot, multi-chunk upload with per-chunk ack `{file_status: "ok"}` |
`pushFile` in [src/protocol/data-channel.ts](../src/protocol/data-channel.ts) implements the upload side: 30 KB chunks, 500 ms breather every 5 chunks, per-chunk timeout 10 s, sequential to keep the response handler chain unambiguous.
### Shared topics
| Topic | Constant | Notes |
|---|---|---|
| `rt/lf/lowstate` | `LOW_STATE` | Battery, motor temps — globally subscribed by `app.ts`; surfaced in the mapping page header |
| `rt/utlidar/switch` | `LIDAR_SWITCH` | Hardware on/off |
## User Flow (Stepper)
The right sidebar mirrors the stepper sequence:
| Step | Section | Goal |
|---|---|---|
| 1 | Mapping | Build a fresh point-cloud map (or load a saved one) |
| 2 | Localization | Tell the robot where it is on that map |
| 3 | Navigation | Drive to a goal or run a patrol loop |
| extra | Autocharge | Dock and charge (manual or automatic) |
Each step is gated:
- Mapping is always available.
- Localization unlocks once a map is loaded (`mapLoaded === true`).
- Navigation unlocks once `mapLoaded && localized`.
- Autocharge unlocks with the same condition as Navigation.
The stepper colour-codes the steps: **green = passed, yellow = current, grey = not reached**.
## Mapping
User clicks **New Map**:
1. `slamScene.clearAll()` + worker `clear` (drops accumulated points).
2. Mint a client-side map ID (`generateMapId()` → 16 random bytes, URL-safe base64). The robot's `mapping/stop` does *not* generate a fresh ID, so we mint our own and push it via `set_map_id` after the stop.
3. Send `mapping/start`.
4. While mapping runs, `cloud_world_ds` packets arrive at ~5 Hz; the SLAM Worker dequantizes them via `libslam.wasm` into Float32Array positions and pushes them into the Three.js point cloud.
User clicks **Stop & Save**:
1. Send `mapping/stop`.
2. On `mapping/stop/success`: clear the live laser cloud, send `common/set_map_id/<minted-id>` to label the freshly-saved map.
3. On `set_map_id/success`: open the Save Map modal.
4. After the user names it, write the metadata to `localStorage` (`go2_slam_maps`) and download the full bundle (PCD + PGM + TXT) into IndexedDB.
The robot only has **one physical map slot** at `/unitree/module/unitree_lidar_slam/data/map_data/`. Subsequent mappings overwrite the same files. That's why we cache locally — see [Map Storage](#map-storage).
## Localization
User drags a pose marker on the map → release fires `localization/set_initial_pose/{x}/{y}/{yaw}` (yaw in radians, CCW from +X). 100 ms later we auto-issue `localization/start`.
Server-log strings drive the UI transitions:
- `[Localization] initialization succeed!` → subscribe LOC topics, show robot model, light up Localization step + Navigation gate.
- `[Localization] initialization failed!` → unsubscribe LOC topics, highlight "Set Initial Pose" with a hint to re-anchor.
Once localized, the live laser scan (`USLAM_LOC_CLOUD`) is rendered in white (dark theme) or accent blue (light theme) on top of the cached map cloud, and the Go2 model tracks `USLAM_LOC_ODOM` with frame-rate-driven smoothing (see [Theme + Smoothing](#theme--smoothing)).
## Navigation
The Navigation step has two tabs: **Go to Goal** and **Patrol**.
### Go to Goal
User holds-and-drags a pose on the map. On release:
1. If `navigationActive === false`, send `navigation/start` first (the robot wouldn't move otherwise — `set_goal_pose` on a stopped nav module just registers the target). This auto-start makes "drag a goal → robot drives there" Just Work.
2. Send `navigation/set_goal_pose/{x}/{y}/{yaw}`.
3. Place a pole marker at the goal pose for visual reference.
Sub-state handling on `navigation/state_transition/<STATE>`:
| State | Behaviour |
|---|---|
| `WAITING` / `TRACKING` | informational only |
| `REACHED` | clear goal marker + path; toast "goal reached"; if `autoChargeOnReach` is armed, fire `autocharge/start` |
| `NO_PATH` | clear path; toast "No path to goal" |
| `TIMEOUT` | toast "Navigation timed out" |
| `GOAL_OCCUPIED` | toast "Goal location is occupied" |
| `GOAL_CHANGED` | toast "Goal changed" |
| `FAILURE` | clear path + goal marker; **no main-state change** — `navigationActive` stays true so the user can dispatch another goal without re-pressing Start |
| `GOAL_CANCELLED` | informational; clear path |
### Patrol
Add waypoints (hold-and-drag), then **Execute Patrol**. The execute sequence matches the APK's `handleSureExecutePatrol`:
```
1. patrol/start ← MUST be first; puts the module in INITIALIZE
2. wait 500 ms
3. patrol/clear_all_patrol_points
4. patrol/add_patrol_point/{x}/{y}/{yaw} × N
5. patrol/set_total_time_limit/{n}
patrol/set_patrol_time_limit/{n}
patrol/set_charge_time_limit/{n}
patrol/set_patrol_number_limit/{n}
patrol/set_bms_soc_limit/{min}/{max}
6. patrol/go
```
> **Important:** `patrol/start` MUST come first. After a previous `patrol/stop` the module is idle and rejects all `add_patrol_point` and `set_*_limit` commands. They only succeed in the INITIALIZE window after `start`. This also means the standalone "Apply" buttons on each limit input *fail when patrol is idle* — the values are persisted in a local `patrolConfig` store and re-sent every Execute Patrol.
Yaw is sent **as-is**, in radians, CCW from +X. There is no `± π` adjustment despite earlier APK code suggesting one — it produces values outside `[−π, π]` that the navigator flags as `GOAL_POINT_UNREACHABLE`. The patrol module persists waypoints to `/unitree/.../map_data/patrol_points.txt`; if that file has stale yaws from an older build, `patrol/start` will load them instead of your fresh `add_patrol_point` calls. Truncate it manually if you suspect drift.
### Patrol Limits
All values stored in a per-page `patrolConfig` and re-sent on every Execute Patrol.
| Limit | Default | Trigger when reached |
|---|---|---|
| `patrol/set_patrol_time_limit/{s}` | `-1` (unlimited) | `REACH_PATROL_TIME_LIMIT` → `NEED_CHARGE` → dock |
| `patrol/set_total_time_limit/{s}` | `-1` (unlimited) | `REACH_TOTAL_TIME_LIMIT` |
| `patrol/set_charge_time_limit/{s}` | `-1` | maximum dock dwell time |
| `patrol/set_patrol_number_limit/{n}` | `-1` (unlimited) | `REACH_PATROL_NUMBER_LIMIT` |
| `patrol/set_bms_soc_limit/{min}/{max}` | `10/80` | `min`: SOC drops below → `NEED_CHARGE`. `max`: SOC charged up to → resume. Binary rejects `0/100` as out-of-range; use `1/99` to effectively disable |
`patrol/clear_user_config` resets all to firmware defaults.
There is **no `get_*_limit` query** — the binary only exposes setters. Current values are cached client-side once Apply is pressed.
## Auto-Charge
The autocharge module is a **lidar-only plate detector** — no AprilTag, no fiducial. It uses PCL `EuclideanClusterExtraction` + `KdTreeFLANN` to look for a flat 300×200 mm cluster at the configured `plate_distance` (default 0.47 m). State sequence:
```
GO_TO_CHARGE_BOARD ← driving up to the dock
CONNECTING ← contact attempt
TIMEOUT_DETECT ← couldn't see/align with the plate
TIMEOUT_CONNECT_POWER ← contacts didn't mate
SUCCESS / FAILURE / EXIT
```
UI flow (Autocharge section):
- **Go to Charging Station**: ensures `navigation/start` + 1 s delay + `navigation/set_goal_pose/-0.150/0.000/0.000`; arms `autoChargeOnReach`. On `navigation/state_transition/REACHED` we then fire `autocharge/start`.
- **Cancel Charge**: sends `navigation/stop` + `autocharge/stop`, clears markers (mirrors APK's `_A`).
- **Plate distance (m)**: `autocharge/set_plate_distance/{m}` — adjust if the dock isn't being detected.
- **Get Status**: `autocharge/get_status` round-trip with 2 s timeout.
- **Disable in Patrol**: one-click sets `bms_soc_limit/1/99 + patrol_time_limit/-1 + charge_time_limit/0 + autocharge/stop` so the patrol module never enters NEED_CHARGE on its own.
Retry on `autocharge/state_transition/FAILURE` is capped at 5 attempts (`AUTO_CHARGE_MAX_RETRIES`); each retry re-issues the dock goal pose and re-arms `autoChargeOnReach`.
## Map Storage
### Why a local cache
The robot has **one physical map slot**. Anything saved there gets overwritten on the next `mapping/start`. The official Unitree app stores maps in their cloud and re-uploads them when the user picks one. We replicate the same architecture with **IndexedDB** instead of cloud, plus a `push_static_file` upload to the robot before each Load.
### IndexedDB layout
Database `go2_slam`, store `pcds`, schema v2.
```ts
// keyed by robot map ID (URL-safe base64)
interface MapBundle {
pcd: ArrayBuffer; // 3D point cloud — required, used to render
pgm?: ArrayBuffer; // 2D occupancy grid (used by the robot, not the viewer)
txt?: ArrayBuffer; // origin/resolution metadata
}
```
[`src/storage/map-pcd-store.ts`](../src/storage/map-pcd-store.ts) wraps the standard CRUD: `putBundle`, `getBundle`, `deleteBundle`, `listBundleIds`. Bundle payloads are stored alongside a `localStorage` index (`go2_slam_maps`) of `{id, name, date}` records that drives the saved-maps UI list.
**Save flow** (after `set_map_id/success`):
1. Save the `{id, name, date}` to localStorage.
2. Sequentially fetch `map.pcd`, `map.pgm`, `map.txt` from the robot via `request_static_file` (sequential — concurrent calls would clobber the data-channel response handler chain).
3. Write the bundle into IndexedDB.
**Load flow:**
1. Read the bundle from IndexedDB.
2. Render the cached PCD immediately for fast feedback.
3. If `robotSlotMapId === mapId`, skip the upload — the robot already has these files. Otherwise upload all three via `pushFile` (chunked `push_static_file`).
4. Send `common/set_map_id/{id}` to make the slot active.
5. Update `robotSlotMapId` so future Loads of the same id can skip the upload.
The fallback path (no cached bundle) renders whatever is currently in the robot's slot **without writing it back to IndexedDB** — `set_map_id` only relabels and the bytes might not actually correspond to that id.
### Zip Import / Export
Each saved-map row has **Export**, and the Saved Maps section header has **Import .zip**.
Zip layout:
```
metadata.json { id, name, date }
map.pcd required
map.pgm optional
map.txt optional
```
Implemented in [`map-pcd-store.ts`](../src/storage/map-pcd-store.ts) using the [`fflate`](https://github.com/101arrowz/fflate) zip codec. Useful for sharing maps between devices, archiving, or hand-editing the PCD with PCL viewers.
## Status Queries
All Promise-based, 2 s timeout, single-resolver pattern matching the APK:
| Query | Topic | Resolves with |
|---|---|---|
| `queryMappingStatus()` | `mapping/get_status` | `'1'` active / `'0'` inactive / `'-1'` timeout |
| `queryLocalizationStatus()` | `localization/get_status` | `'1'` / `'0'` / `'-1'` |
| `queryPatrolStatus()` | `patrol/get_status` | `'1'` / `'0'` / `'-1'` |
| `queryNavigationStatus()` | `navigation/get_status` | `'1'` / `'0'` / `'-1'` |
| `queryAutoChargeStatus()` | `autocharge/get_status` | `'1'` / `'0'` / `'-1'` |
| `queryCurrentMapId()` | `common/get_map_id` | id string or empty on timeout |
`preloadRobotState()` runs all five in parallel on page entry, then restores UI state:
- `mapping=1` → show mapping in progress.
- `loc=1` → mark localized, subscribe LOC topics, fetch active map id, render its cached PCD.
- `patrol=1` → enter patrol mode, request waypoints back from the robot.
- `nav=1` → mark `navigationActive`, switch the nav controls to Stop.
- `autocharge=1` → log "autocharge already running".
## Theme + Smoothing
- The 3D scene reacts to dark/light toggle: `SlamScene` listens to `theme().onChange()` and re-tints the renderer clear color, scene background, grid lines, and the live laser cloud (white on dark, accent blue on light).
- Robot-pose smoothing is **render-driven**, not event-driven. Odom updates are throttled to 50 ms (matches APK at 20 Hz) and only set a `targetPos` / `targetQuat`; the `animate()` loop lerps the robot model toward that target every render frame at `0.15`. This absorbs per-sample SLAM jitter and produces a continuously-moving model regardless of input rate.
## Known Quirks
- `patrol/stop` also stops the navigation module on the firmware side; we sync `navigationActive = false` on either `patrol/stop/success` or a standalone `navigation/stop/success`.
- `patrol/pause` aborts the current navigation goal (`GOAL_CANCELLED → FAILURE`); resume re-plans from the current pose, which can occasionally fail one segment before recovering.
- `set_bms_soc_limit/0/100` is rejected; use `1/99` to effectively disable battery-triggered docking.
- `patrol/start` auto-loads from `/unitree/.../map_data/patrol_points.txt`. If the file is stale (e.g. from an older build that subtracted π from yaws), the robot uses the file values, not your in-memory `add_patrol_point` calls.
- Setting a goal in Goal mode auto-fires `navigation/start` if not running, so a fresh page can drag-to-go without an explicit Start press.
- Yaw values must stay within `[−π, π]`; values outside that range are flagged `GOAL_POINT_UNREACHABLE`.
- The robot does *not* mint a fresh map id on `mapping/stop` — we mint client-side and push via `set_map_id` to keep saves distinct.
## Files
| File | Role |
|---|---|
| [src/ui/components/mapping-page.ts](../src/ui/components/mapping-page.ts) | The whole feature — UI, state machine, command dispatch, server-log parsing |
| [src/ui/scene/slam-scene.ts](../src/ui/scene/slam-scene.ts) | Three.js scene: ground plane, point clouds, robot marker, goal/patrol markers, drag-to-set-pose |
| [src/ui/scene/robot-model.ts](../src/ui/scene/robot-model.ts) | Go2 GLB model with joint-sync from `LowState` |
| [src/workers/slam-worker.ts](../src/workers/slam-worker.ts) | `libslam.wasm` bridge (cloud dequantization) |
| [src/storage/map-pcd-store.ts](../src/storage/map-pcd-store.ts) | IndexedDB CRUD + zip import/export |
| [src/protocol/data-channel.ts](../src/protocol/data-channel.ts) | `requestFile` (download) + `pushFile` (chunked upload) |
| [src/protocol/topics.ts](../src/protocol/topics.ts) | `RTC_TOPIC.USLAM_*` constants |

View File

@@ -0,0 +1,44 @@
# Robot Status
Live diagnostics panel — battery, motors, IMU, system, network — with family-aware fields for Go2 and G1.
<p align="center">
<img src="../images/status_go2.png" width="48%" />
<img src="../images/status_g1.png" width="48%" />
</p>
## Sections
### Battery
- **Go2** — pack voltage from `lowstate.power_v` (volts at the root, multiplied to mV in the UI), plus `bms_state.bq_ntc` / `mcu_ntc` for cell temperatures, SOC %, current, cycle count.
- **G1** — `bms_state.bmsvoltage` array: `[0]` is pack voltage, `[1]` is cell voltage. Plus per-rail temperatures: MOS, BAT1, RES.
### Motors
Per-joint table: temperature, position, velocity, torque, lost packets. Live-updates from `rt/lowstate`.
### IMU
Quaternion + RPY + gyroscope + accelerometer.
### LiDAR
Operational state, work mode, motor temp.
### System
Family-aware merge of firmware + system info:
- **Go2** — Package Version (single combined firmware string fetched via bashrunner).
- **G1** — Hardware Version + Software Version (separate fields, fetched from the device info API).
The **Connection Type** row (Local / AP / Remote, and which IP/SN) lives at the bottom of this section. The legacy "Network" card was folded in.
## Bashrunner Fetch (Go2)
Go2's package version isn't surfaced through any data-channel topic. The UI calls a small `bashrunner` RPC to read `/etc/unitree/version` (or equivalent), correlates the response by request ID where possible, and falls back to setting the firmware string on any matching reply — Go2 isn't always strict about echoing the ID back.
## Copy Buttons
Most labelled fields have a clipboard copy button next to the value (SN, IP, MAC, version strings). Click → green check; failure → red.

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 795 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 653 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 KiB

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<title>Unitree UI</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
{
"name": "unitree-ui",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"dev:host": "vite --host",
"start": "bash start.sh",
"start:no-ble": "bash start.sh --no-ble",
"ble-server": "python3 server/ble_server.py",
"scanner": "node server/scanner.mjs",
"build": "tsc && vite build",
"preview": "vite preview"
},
"devDependencies": {
"@types/node-forge": "^1.3.14",
"@types/three": "^0.183.1",
"typescript": "^5.7.0",
"vite": "^6.0.0"
},
"dependencies": {
"fflate": "^0.8.2",
"node-forge": "^1.3.1",
"three": "^0.183.2"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><defs><path id="dance1_a" d="M0 0h63v66H0z" /></defs><g fill-rule="evenodd"><g transform="translate(27 48)"><mask id="dance1_b" fill="#fff"><use xlink:href="#dance1_a" /></mask><path d="M36.668 9.698c.502 9.03 1.388 17.598 1.316 26.156-.053 6.27 4.19 9.88 6.996 14.432 1.105 1.792 2.475 3.417 3.683 5.147 1.143 1.637 1.02 3.338-.464 4.569-1.593 1.322-3.508 1.149-4.738-.509-3.725-5.022-7.531-10.015-10.81-15.327-1.799-2.914-.624-6.692-1.322-10.01-.24-1.135.733-2.9-.916-3.298-2.089-.504-1.945 1.65-2.468 2.843-1.121 2.558-2.084 5.187-3.213 7.742-.866 1.962-.857 3.726-.014 5.79 1.92 4.706 3.532 9.54 5.224 14.338.693 1.964-.211 3.425-1.929 4.148-1.847.778-3.254-.13-4.133-1.868a8.816 8.816 0 0 1-.406-.932c-2.052-5.565-4.08-11.139-6.159-16.693-.654-1.748-.245-3.383.46-4.926 2.25-4.934 3.402-10.316 6.119-15.053.328-.572.227-1.143.186-1.73-.18-2.538-.357-5.076-.576-8.183-.959 1.769-1.72 3.114-2.426 4.49-.907 1.77-2.29 2.347-4.197 1.896-4.526-1.07-9.065-2.087-13.589-3.17C.898 18.979-.33 17.372.077 15.492c.485-2.243 2.002-3.127 4.668-2.537 3.213.711 6.413 1.49 9.591 2.34 1.436.385 2.248.158 2.944-1.33 1.762-3.764 3.773-7.411 5.617-11.139.71-1.436 1.675-2.32 3.333-2.259.085.003.176.018.253-.007 6.39-2.007 11.124 1.818 16.076 4.702 4.942 2.878 4.882 2.934 9.563-.535 1.83-1.357 3.766-2.57 5.618-3.9 1.68-1.208 3.235-1.028 4.468.531 1.248 1.58.969 3.45-.471 4.566-4.066 3.149-8.303 6.075-12.512 9.033-1.507 1.06-2.926.176-4.247-.546-2.655-1.452-5.268-2.981-8.31-4.712" mask="url(#dance1_b)" /></g><path d="M97.935 71.37c0 2.306.22 4.637-.05 6.912-.433 3.647-3.77 5.35-8.327 4.505-3.237-.6-5.046-3.096-4.443-6.05.507-2.486 2.37-3.194 4.588-3.5 5.137-.71 5.137-.712 5.137-5.833 0-2.64.047-5.282-.017-7.92-.042-1.76.603-2.558 2.515-2.479 3.292.138 6.26 1.927 9.63 1.679.756-.056.98.797 1.007 1.493.029.742.027 1.485.022 2.227-.017 2.657-.648 3.174-3.36 2.568-1.47-.328-2.9-.832-4.348-1.254-1.563-.455-2.192.213-2.178 1.71.018 1.98.004 3.96.004 5.941h-.18M47.005 38.403c.15-4.243 3.567-7.566 7.608-7.397 4.227.177 7.584 3.794 7.378 7.951-.203 4.077-3.77 7.23-7.958 7.034-4.117-.19-7.173-3.491-7.028-7.588" /></g></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><defs><path id="dance2_a" d="M0 0h60v62H0z" /><path id="dance2_c" d="M0 0h11v33H0z" /></defs><g fill-rule="evenodd"><g transform="translate(28 30)"><mask id="dance2_b" fill="#fff"><use xlink:href="#dance2_a" /></mask><path d="M31.491 61.136c-5.406 1.344-10.793 1.016-16.147-.271-.51-.123-1.383.04-1.523-.632-.154-.738.522-1.224 1.088-1.62 2.878-2.017 5.773-4.01 8.651-6.026 1.756-1.23 4.24-2.145 5.026-3.831.812-1.74-1.632-3.533-2.299-5.453-1.465-4.225-2.672-8.53-3.033-12.99-.115-1.427-.496-1.813-1.853-1.977-6.01-.726-11.996-1.68-18.014-2.326-2.454-.264-3.721-.818-3.311-3.665.225-1.559.723-1.723 2.042-1.727 9.46-.022 18.92-.203 28.379-.12 2.206.019 3.594-.793 4.77-2.392 4.08-5.54 8.162-11.076 12.22-16.632 1.525-2.09 4.856-1.936 5.972.329.35.71-.032 1.333-.372 1.923-4.242 7.354-8.613 14.639-12.677 22.089-1.515 2.776-.33 5.697 1.045 8.34 2.082 4.001 2.58 4.092 6.175 1.451 3.383-2.484 6.764-4.97 10.183-7.404.503-.358 1.103-1.066 1.827-.536.542.398.332 1.144.254 1.704-.758 5.428-1.772 10.778-5.335 15.241.09.494-.126.894-.391 1.28-.517.587-1.07 1.15-1.336 1.913a3.23 3.23 0 0 1-.772 1.116c-.169.15-.352.283-.541.407-.956.59-1.673 1.443-2.463 2.215-.23.246-.47.48-.712.713-.828 1.128-2.212 1.496-3.245 2.343l-1.577 1.52c-.825 1.586-2.461 1.958-3.92 2.598-.812.283-1.59.702-2.491.643-1.927.426-3.805 1.002-5.62 1.777" mask="url(#dance2_b)" /></g><g transform="translate(61 83)"><mask id="dance2_d" fill="#fff"><use xlink:href="#dance2_c" /></mask><path d="M10.616 3.642c-1.532 8.703-3.094 17.4-4.576 26.11-.467 2.745-2.25 3.846-4.93 2.933-.97-.33-.683-1.035-.743-1.648-.54-5.4-.117-10.817-.244-16.224-.027-1.184.064-2.374-.016-3.553-.235-3.451-.472-6.872 3.88-8.194.733-.666 1.66-1.007 2.521-1.463C8.043 1.346 9.035-.923 11 .425c-.127 1.073.253 2.204-.384 3.217" mask="url(#dance2_d)" /></g><path d="M77.101 75.637c.753-.58 1.614-.637 2.507-.635 6.405.012 12.81.008 19.214.004 1.057 0 2.36-.188 2.008 1.577-.308 1.536 1.285 3.938-1.571 4.423-9.317 1.581-18.639 3.126-27.958 4.685-.784.38-1.55.493-2.272-.15-.279-1.924 1.535-2.334 2.278-3.52 1.116-.786 1.73-2.026 2.513-3.115 1.27-.892 1.932-2.466 3.281-3.269M56.244 48C53.095 47.95 49.96 44.673 50 41.47c.047-3.736 2.985-6.56 6.729-6.469 3.206.078 6.303 3.271 6.27 6.465-.035 3.5-3.226 6.586-6.755 6.532M59 91c1.731-1.442 3.854-1.708 5.967-2 .08.225.015.378-.22.446C62.78 89.776 61.04 90.95 59 91" /><path d="M76 82c-.128 1.509-1.491 1.799-2.15 2.785L73 85c.623-1.482 1.984-2.02 3-3M80 79c-.255 1.236-1.471 1.803-1.97 2.87L77 82c.737-1.267 1.716-2.288 3-3M81.994 76c.105 1.071-1.15 1.429-1.994 2 .009-1.011.928-1.544 1.994-2" /></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 123" fill="#fff"><path d="M78.814 45.028c-3.349-.542-11.4-5.97-17.19-4.16-5.789 1.809-5.335 3.257-8.23 4.162-2.896.905-3.348-.542-9.137 1.267-5.788 1.809-9.316 10.855-11.758 13.208 0 0-2.415 2.442-1.538 5.247.878 2.803 3.8 1.988 3.8 1.988 10.133-3.166 13.57-7.419 13.57-7.419s5.245 1.538 9.586.181c4.342-1.357 7.78-5.61 7.78-5.61s5.248 1.538 15.378-1.628c0 0 2.866-.996 1.989-3.8-.878-2.804-4.25-3.436-4.25-3.436Zm-1.63 10.042c-8.683 2.713-6.872 8.502-17.004 11.67-10.132 3.167-11.942-2.62-20.625.092-2.894.906-3.89 2.804-3.436 4.255 1.356 4.34 12.665 15.101 28.586 10.127 15.92-4.976 19.086-20.264 17.73-24.604-.454-1.45-2.357-2.445-5.25-1.54Z" fill-rule="nonzero" stroke-width="0" /></svg>

After

Width:  |  Height:  |  Size: 791 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 123" fill="#fff"><path d="M44.914 55.457a3.362 3.362 0 0 0-4.7-.671 3.362 3.362 0 0 0-.671 4.7l3.997 5.33c.504.67.43 1.604-.157 2.192L42.04 68.35a1.668 1.668 0 0 1-2.476-.116l-9.421-11.31V41.358c0-1.857-1.5-3.357-3.357-3.357a3.354 3.354 0 0 0-3.357 3.357V64.27c0 1.143.388 2.255 1.101 3.147l10.921 14.09a6.262 6.262 0 0 1 1.091 2.276c.19.724.85 1.217 1.606 1.217h13.816c.923 0 1.679-.755 1.679-1.679v-11.75a13.45 13.45 0 0 0-2.686-8.057l-6.043-8.057ZM87.214 38a3.354 3.354 0 0 0-3.357 3.357v15.569l-9.42 11.31a1.685 1.685 0 0 1-2.477.115l-1.343-1.343a1.677 1.677 0 0 1-.157-2.193l3.997-5.33a3.362 3.362 0 0 0-.671-4.7 3.362 3.362 0 0 0-4.7.672l-6.043 8.057a13.45 13.45 0 0 0-2.686 8.057v11.75c0 .924.755 1.679 1.679 1.679h13.816c.745 0 1.417-.493 1.606-1.217a6.262 6.262 0 0 1 1.09-2.277L89.47 67.417a5.041 5.041 0 0 0 1.101-3.147V41.357c0-1.857-1.5-3.357-3.357-3.357Z" fill-rule="nonzero" stroke-width="0" /></svg>

After

Width:  |  Height:  |  Size: 1015 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 123.2" fill="#fff"><defs><path id="icon_active_bow_white_a" d="M0 0h37v71H0z" /></defs><g fill-rule="evenodd"><path d="M55 32a7 7 0 1 0 0-14 7 7 0 0 0 0 14M55.287 34.537h-.574c-.047-.301-.407-.537-.852-.537-.475 0-.861.268-.861.599v.802c0 .33.384.599.861.599.462 0 .838-.257.856-.575h.563c.02.32.394.575.859.575.474 0 .861-.268.861-.6V34.6c0-.33-.386-.599-.861-.599-.447 0-.807.236-.852.537" /><g transform="translate(39 34)"><mask id="icon_active_bow_white_b" fill="#fff"><use xlink:href="#icon_active_bow_white_a" /></mask><path d="M26.892 2.67a4.704 4.704 0 0 0-1.89-1.164C23.802 1.046 21.201.153 19.95 0l-3.721 5.323L12.753.35c-1.99.101-2.77-.073-4.912.713C4.231 2.39 1.524 11.599.253 15.228c-1.27 3.629 2.519 5.89 5.312 6.642h1.396l12.846-5.127c2.596-1.454 1.876-3.933 1.844-4.038-.573-1.776-1.962-2.233-3.026-2.303a3.398 3.398 0 0 0-1.497.248l-9.25 3.692a.477.477 0 0 1-.351-.886l9.25-3.691a4.373 4.373 0 0 1 1.91-.313c1.111.072 3.092.554 3.87 2.966.01.036 1 3.338-2.316 5.173l-.054.026-12.484 4.982v2.007l-.435 41.45A4.94 4.94 0 0 0 12.202 71c2.724 0 3.134-2.215 3.134-4.945l1.35-22.483s-.427-1.593.378-1.715c.804-.122.552 1.442.552 1.442l.865 23.323c.202 2.303 2.267 4.457 4.565 4.256 2.298-.204 3.957-2.687 3.757-4.99l-.934-49.564c.024-.356.04-.713.063-1.068 1.791 3.721 2.265 7.799 2.023 12.756-.282 5.819 8.717 5.792 8.998 0C37.446 17.908 34.05 9.744 26.892 2.67" mask="url(#icon_active_bow_white_b)" /></g></g></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 123" fill="#fff"><g fill-rule="nonzero" stroke-width="0"><path d="M77.794 73.989c-.123 4.814-2.224 8.792-5.263 12.375-2.618 3.08-5.617 5.596-9.649 6.603-4.338 1.088-8.493.455-12.511-1.388-3.38-1.55-6.331-3.712-8.956-6.33-4.842-4.836-9.681-9.675-14.517-14.517-.768-.769-1.374-1.578-.993-2.795.707-2.258 2.972-2.917 4.692-1.258 2.353 2.278 4.644 4.624 6.963 6.936.258.258.517.666.81.693.618.061 1.454.136 1.842-.204.333-.292.265-1.162.177-1.747-.048-.327-.483-.612-.762-.898-4.005-4.025-8.03-8.03-12.022-12.07-1.577-1.59-1.033-4.059.993-4.616 1.333-.367 2.387.17 3.325 1.122 3.651 3.705 7.337 7.39 11.009 11.076.258.259.51.53.795.741.64.47 1.306.45 1.89-.088.599-.55.613-1.224.191-1.877-.252-.387-.605-.727-.938-1.06-4.862-4.869-9.73-9.73-14.579-14.606-1.761-1.775-1.204-4.27 1.074-4.896 1.13-.313 1.993.204 2.74.945 2.646 2.611 5.27 5.23 7.895 7.854 2.34 2.346 4.665 4.705 7.004 7.044.306.306.632.748 1 .803.537.088 1.23.054 1.658-.231.585-.381.7-1.102.252-1.741a7.302 7.302 0 0 0-.85-.986c-3.8-3.821-7.616-7.623-11.403-11.45-1.598-1.612-1.136-4.02.884-4.645 1.326-.408 2.387.095 3.332 1.054 3.8 3.828 7.615 7.63 11.423 11.437 2.775 2.781 5.535 5.59 8.344 8.336.428.415 1.108.823 1.665.81 1.014-.014 1.503-1.21 1.02-2.251-1.244-2.672-2.556-5.31-3.746-7.996-.354-.803-.578-1.741-.565-2.612.028-1.835 1.931-2.991 3.57-2.203.626.306 1.313.782 1.646 1.367 3.087 5.433 6.174 10.873 9.111 16.394 1.116 2.088 1.483 4.46 1.449 6.875Z" /><path d="M72.313 92.042c1.442-1.646 2.938-3.19 4.23-4.882 3.107-4.06 5.031-8.588 4.882-13.804-.109-3.848-1.285-7.425-3.176-10.764a1045.897 1045.897 0 0 0-7.044-12.307c-.599-1.027-.898-1.952-.095-2.999.822-1.074 1.87-1.564 3.223-1.373 1.55.224 2.516 1.21 3.243 2.495 1.319 2.305 2.633 4.613 3.944 6.922 1.312 2.305 2.57 4.651 3.944 6.922 4.365 7.201 3.29 15.123-1.537 21.637-2.645 3.577-5.786 6.541-10.18 7.86-.38.116-.763.224-1.148.327-.075.013-.15-.014-.286-.034ZM60.04 47.477c-1.449 1.53-1.945 3.365-1.646 5.623-.299-.218-.442-.3-.557-.415-3.271-3.264-6.508-6.555-9.812-9.778-.755-.734-.694-1.319-.163-2.074 1.074-1.53 3.522-2.053 4.922-.74 2.482 2.325 4.787 4.854 7.256 7.384Zm3.74-13.505c.32-2.556.598-4.814.897-7.248 1.272.55 2.42 1.04 3.754 1.618-1.544 1.87-3.013 3.638-4.651 5.63Zm-13.056-.435c-1.387-1.673-2.774-3.345-4.297-5.174 1.25-.578 2.4-1.109 3.726-1.72.292 2.386.564 4.596.83 6.799-.082.034-.17.061-.259.095ZM59.32 25.5l-1.877 6.093a2.043 2.043 0 0 1-.204.013c-.618-1.999-1.237-3.998-1.897-6.106h3.978Z" /></g></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><defs><path id="icon_active_dance1_white_a" d="M0 0h63v66H0z" /></defs><g fill-rule="evenodd"><g transform="translate(27 48)"><mask id="icon_active_dance1_white_b" fill="#fff"><use xlink:href="#icon_active_dance1_white_a" /></mask><path d="M36.668 9.698c.502 9.03 1.388 17.598 1.316 26.156-.053 6.27 4.19 9.88 6.996 14.432 1.105 1.792 2.475 3.417 3.683 5.147 1.143 1.637 1.02 3.338-.464 4.569-1.593 1.322-3.508 1.149-4.738-.509-3.725-5.022-7.531-10.015-10.81-15.327-1.799-2.914-.624-6.692-1.322-10.01-.24-1.135.733-2.9-.916-3.298-2.089-.504-1.945 1.65-2.468 2.843-1.121 2.558-2.084 5.187-3.213 7.742-.866 1.962-.857 3.726-.014 5.79 1.92 4.706 3.532 9.54 5.224 14.338.693 1.964-.211 3.425-1.929 4.148-1.847.778-3.254-.13-4.133-1.868a8.816 8.816 0 0 1-.406-.932c-2.052-5.565-4.08-11.139-6.159-16.693-.654-1.748-.245-3.383.46-4.926 2.25-4.934 3.402-10.316 6.119-15.053.328-.572.227-1.143.186-1.73-.18-2.538-.357-5.076-.576-8.183-.959 1.769-1.72 3.114-2.426 4.49-.907 1.77-2.29 2.347-4.197 1.896-4.526-1.07-9.065-2.087-13.589-3.17C.898 18.979-.33 17.372.077 15.492c.485-2.243 2.002-3.127 4.668-2.537 3.213.711 6.413 1.49 9.591 2.34 1.436.385 2.248.158 2.944-1.33 1.762-3.764 3.773-7.411 5.617-11.139.71-1.436 1.675-2.32 3.333-2.259.085.003.176.018.253-.007 6.39-2.007 11.124 1.818 16.076 4.702 4.942 2.878 4.882 2.934 9.563-.535 1.83-1.357 3.766-2.57 5.618-3.9 1.68-1.208 3.235-1.028 4.468.531 1.248 1.58.969 3.45-.471 4.566-4.066 3.149-8.303 6.075-12.512 9.033-1.507 1.06-2.926.176-4.247-.546-2.655-1.452-5.268-2.981-8.31-4.712" mask="url(#icon_active_dance1_white_b)" /></g><path d="M97.935 71.37c0 2.306.22 4.637-.05 6.912-.433 3.647-3.77 5.35-8.327 4.505-3.237-.6-5.046-3.096-4.443-6.05.507-2.486 2.37-3.194 4.588-3.5 5.137-.71 5.137-.712 5.137-5.833 0-2.64.047-5.282-.017-7.92-.042-1.76.603-2.558 2.515-2.479 3.292.138 6.26 1.927 9.63 1.679.756-.056.98.797 1.007 1.493.029.742.027 1.485.022 2.227-.017 2.657-.648 3.174-3.36 2.568-1.47-.328-2.9-.832-4.348-1.254-1.563-.455-2.192.213-2.178 1.71.018 1.98.004 3.96.004 5.941h-.18M47.005 38.403c.15-4.243 3.567-7.566 7.608-7.397 4.227.177 7.584 3.794 7.378 7.951-.203 4.077-3.77 7.23-7.958 7.034-4.117-.19-7.173-3.491-7.028-7.588" /></g></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><defs><path id="icon_active_dance2_white_a" d="M0 0h60v62H0z" /><path id="icon_active_dance2_white_c" d="M0 0h11v33H0z" /></defs><g fill-rule="evenodd"><g transform="translate(28 30)"><mask id="icon_active_dance2_white_b" fill="#fff"><use xlink:href="#icon_active_dance2_white_a" /></mask><path d="M31.491 61.136c-5.406 1.344-10.793 1.016-16.147-.271-.51-.123-1.383.04-1.523-.632-.154-.738.522-1.224 1.088-1.62 2.878-2.017 5.773-4.01 8.651-6.026 1.756-1.23 4.24-2.145 5.026-3.831.812-1.74-1.632-3.533-2.299-5.453-1.465-4.225-2.672-8.53-3.033-12.99-.115-1.427-.496-1.813-1.853-1.977-6.01-.726-11.996-1.68-18.014-2.326-2.454-.264-3.721-.818-3.311-3.665.225-1.559.723-1.723 2.042-1.727 9.46-.022 18.92-.203 28.379-.12 2.206.019 3.594-.793 4.77-2.392 4.08-5.54 8.162-11.076 12.22-16.632 1.525-2.09 4.856-1.936 5.972.329.35.71-.032 1.333-.372 1.923-4.242 7.354-8.613 14.639-12.677 22.089-1.515 2.776-.33 5.697 1.045 8.34 2.082 4.001 2.58 4.092 6.175 1.451 3.383-2.484 6.764-4.97 10.183-7.404.503-.358 1.103-1.066 1.827-.536.542.398.332 1.144.254 1.704-.758 5.428-1.772 10.778-5.335 15.241.09.494-.126.894-.391 1.28-.517.587-1.07 1.15-1.336 1.913a3.23 3.23 0 0 1-.772 1.116c-.169.15-.352.283-.541.407-.956.59-1.673 1.443-2.463 2.215-.23.246-.47.48-.712.713-.828 1.128-2.212 1.496-3.245 2.343l-1.577 1.52c-.825 1.586-2.461 1.958-3.92 2.598-.812.283-1.59.702-2.491.643-1.927.426-3.805 1.002-5.62 1.777" mask="url(#icon_active_dance2_white_b)" /></g><g transform="translate(61 83)"><mask id="icon_active_dance2_white_d" fill="#fff"><use xlink:href="#icon_active_dance2_white_c" /></mask><path d="M10.616 3.642c-1.532 8.703-3.094 17.4-4.576 26.11-.467 2.745-2.25 3.846-4.93 2.933-.97-.33-.683-1.035-.743-1.648-.54-5.4-.117-10.817-.244-16.224-.027-1.184.064-2.374-.016-3.553-.235-3.451-.472-6.872 3.88-8.194.733-.666 1.66-1.007 2.521-1.463C8.043 1.346 9.035-.923 11 .425c-.127 1.073.253 2.204-.384 3.217" mask="url(#icon_active_dance2_white_d)" /></g><path d="M77.101 75.637c.753-.58 1.614-.637 2.507-.635 6.405.012 12.81.008 19.214.004 1.057 0 2.36-.188 2.008 1.577-.308 1.536 1.285 3.938-1.571 4.423-9.317 1.581-18.639 3.126-27.958 4.685-.784.38-1.55.493-2.272-.15-.279-1.924 1.535-2.334 2.278-3.52 1.116-.786 1.73-2.026 2.513-3.115 1.27-.892 1.932-2.466 3.281-3.269M56.244 48C53.095 47.95 49.96 44.673 50 41.47c.047-3.736 2.985-6.56 6.729-6.469 3.206.078 6.303 3.271 6.27 6.465-.035 3.5-3.226 6.586-6.755 6.532M59 91c1.731-1.442 3.854-1.708 5.967-2 .08.225.015.378-.22.446C62.78 89.776 61.04 90.95 59 91" /><path d="M76 82c-.128 1.509-1.491 1.799-2.15 2.785L73 85c.623-1.482 1.984-2.02 3-3M80 79c-.255 1.236-1.471 1.803-1.97 2.87L77 82c.737-1.267 1.716-2.288 3-3M81.994 76c.105 1.071-1.15 1.429-1.994 2 .009-1.011.928-1.544 1.994-2" /></g></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 123.2" fill="#fff"><defs><path id="icon_active_forwardPush_white_a" d="M0 0h48v74H0z" /></defs><g fill-rule="evenodd"><path d="M59.5 39a8.5 8.5 0 1 0 0-17 8.5 8.5 0 0 0 0 17" /><g transform="translate(35 34)"><mask id="icon_active_forwardPush_white_b" fill="#fff"><use xlink:href="#icon_active_forwardPush_white_a" /></mask><path d="M46.037.083c-2.476-.612-3.792 2.366-3.792 2.366l-4.1 9.968c-.199.6-.93.626-1.637.489a4.651 4.651 0 0 1-.38-.091l2.324-5.616c1.852-5.157-1.268-6.001-1.268-6.001-2.339-.579-3.582 2.236-3.582 2.236l-2.408 5.852-1.768-1.455a8.378 8.378 0 0 0-4.195-1.847c-1.92-.26-3.44.057-3.44.057-4.85.522-6.603 8.78-6.603 8.78l-2.27 13.587c-1.135 4.495-2.064 8.99-2.064 8.99l-1.341 9.406c-.103 1.464-1.548 4.39-1.548 4.39L.743 66.141c-2.063 4.494.825 6.167.825 6.167 3.612.836 5.262-2.3 5.262-2.3L14.26 55.48c1.548-2.195 1.754-4.913 1.754-4.913l1.444-10.557c.413-2.09 1.032-1.986 1.032-1.986l6.707 12.02c1.444 1.986 1.96 4.704 1.96 4.704l2.682 15.47c.62 4.285 4.127 3.762 4.127 3.762 3.302-.94 2.477-5.958 2.477-5.958L33.45 54.643c-.413-4.494-2.477-7.525-2.477-7.525l-4.54-9.303c-.928-1.881-.515-3.553-.515-3.553l1.755-12.964-3.783-3.07a.18.18 0 0 1-.027-.25.174.174 0 0 1 .247-.028l3.79 3.076.073.034c1.609.814 3.168.726 4.107.14.776-.485.95-.897 1.29-1.717l2.917 1.624c1.702.862 3.353.769 4.346.148.854-.534 1.02-.983 1.408-1.92l5.337-12.9C49.338.977 46.037.083 46.037.083" mask="url(#icon_active_forwardPush_white_b)" /></g></g></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><path d="M64.5 36C83.554 36 99 51.446 99 70.5 99 89.554 83.554 105 64.5 105 45.446 105 30 89.554 30 70.5 30 51.446 45.446 36 64.5 36Zm15.813 31.625a2.875 2.875 0 0 0-2.876 2.875c0 7.145-5.792 12.938-12.937 12.938S51.562 77.644 51.562 70.5a2.875 2.875 0 0 0-5.75 0c0 10.321 8.367 18.688 18.688 18.688 10.321 0 18.688-8.367 18.688-18.688a2.875 2.875 0 0 0-2.876-2.875Zm-7.907-6.153a3.393 3.393 0 1 0 0-6.785 3.393 3.393 0 0 0 0 6.785Zm-15.812 0a3.393 3.393 0 1 0 0-6.785 3.393 3.393 0 0 0 0 6.785Z" fill-rule="nonzero" /></svg>

After

Width:  |  Height:  |  Size: 642 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 123" fill="#fff"><path d="M66.843 30.5c1.177-.013 2.21.81 2.432 1.961l.052.484V55.44h2.484V36.566a2.442 2.442 0 0 1 2.235-2.47c1.281-.119 2.444.757 2.667 2.012l.039.47v18.876h2.457v-9.163c0-1.346 1.111-2.444 2.47-2.444 1.203.026 2.197.875 2.393 2.013l.04.431-.197 18.863c0 9.96-1.673 12.68-3.477 15.699-1.739 2.902-3.542 2.052-3.856 10.536l-.026 1.111h-20.89c-.13-9.948-1.973-9.922-3.62-13.503a23.916 23.916 0 0 1-2.51-9.935V53.52c-.039-1.281.941-2.38 2.236-2.497 1.294-.118 2.47.771 2.666 2.04l.04.483v9.163h2.47V37.77c-.026-1.282.954-2.367 2.248-2.484 1.294-.118 2.458.77 2.667 2.039l.04.444v17.673h2.47V32.945a2.472 2.472 0 0 1 2.47-2.445Zm-19.647 0c1.216 0 2.223.863 2.432 2l.039.445v13.307h2.484V35.376c0-1.346 1.098-2.431 2.47-2.431.784 0 1.517.366 1.974.993a4.856 4.856 0 0 0-1.922 3.164l-.052.64v11.556a4.969 4.969 0 0 0-4.614-.17 4.82 4.82 0 0 0-2.706 3.699l-.04.64V67.52a25.614 25.614 0 0 0 2.746 12.379c1.516 3.412 3.124 3.124 3.372 11.346l.027 1.177h-17.4l-.025-1.112c-.249-8.758-2.013-8.875-3.582-12.3a23.523 23.523 0 0 1-2.51-9.935v-15.53c-.026-1.28.955-2.365 2.249-2.483 1.294-.117 2.457.772 2.666 2.04l.04.444v9.163h2.47V37.77c-.13-1.347.876-2.537 2.235-2.654 1.36-.118 2.576.862 2.693 2.209l.013.444v17.673h2.47V32.945c0-1.347 1.112-2.445 2.471-2.445Z" fill-rule="nonzero" stroke-width="0" /></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 123" fill="#fff"><defs><path id="icon_active_hightWave_white_a" d="M0 0h51.108v94.002H0z" /></defs><g stroke-width="0" fill-rule="evenodd"><path d="M58.432 35.462a7.734 7.734 0 1 0 0-15.47 7.734 7.734 0 0 0 0 15.47" /><path d="M84.76 15.32s-3.366-2.481-4.997 1.18l-.781 3.479-.855-1.87s-.32-.665-1.134-.413c0 0-.8.267-.484 1.156 0 0 1.289 3.925 1.746 4.456 0 0-.345.019-.826 1.327 0 0-3.271 7.386-6.74 11.035 0 0-1.502 2.41-6.222 1.337 0 0-9.497-1.982-16.865 1.596 0 0-3.846 1.872-5.772 7.927 0 0-1.487 4.073-4.074 14.147l-2.146 8.367s-1.267 3.467 2.31 4.513c0 0 3.249 1.21 4.405-2.807l5.615-20.257s.605-.77.66.496v54.715s-.33 2.807 1.927 2.752H54.6s2.532.496 2.422-2.532V75.319s.12-1.486 1.437-1.486c0 0 1.206 0 1.26 1.486v30.99s-.275 2.153 1.707 2.177h5.009s1.872.356 1.817-2.342V55.833s-.726-5.953 2.967-9.252c0 0 10.809-11.07 12.37-20.01 0 0 .653-1.757.144-2.06 0 0 2.529-3.021 2.758-5.914 0 0 .245-2.104-1.731-3.276" /></g></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 115 124" fill="#fff"><g stroke-width="0" fill-rule="evenodd"><path d="M31.704 89.573v-.212l-.005-.154c-.128-3.505-.376-15.574 1.828-23.745.195-.725.744-1.407 1.517-2.033 3.89 1.989 12.248 4.825 14.924 6.226.876.459 1.264 2.127 1.45 4.222-1.355-.111-2.556-.342-3.494-.53l-.019-.002c-2.102-.42-4.539.944-5.673 3.172-1.06 2.083-.622 4.313 1.17 5.964 1.569 1.445 4.839 2.046 8.25 2.118v5.297H33.263c-1.487 0-1.559-.117-1.559-.323Zm12-33.267c-2.822-1.784-4.894-4.677-5.684-7.94-.76-3.142-.304-6.311 1.286-8.922 1.565-2.57 4.045-4.288 6.982-4.837a10.782 10.782 0 0 1 1.985-.183c3.601 0 7.057 1.79 9.387 4.67a13.923 13.923 0 0 0-2.416 5.119c-.962 4.03-.004 7.952 2.252 10.776a10.523 10.523 0 0 1-1.718 1.438l-.321.213v3.219l.333.036a20.483 20.483 0 0 1-6.313-1.09 646.633 646.633 0 0 1-5.436-1.862v-.424l-.337-.213Zm34.003-.38c.333-.115.798-.168 1.316-.168 1.548 0 3.567.476 4.003 1.186.634 1.032 1.214 3.21-.63 4.83-1.326 1.165-6.933 2.479-9.626 1.26-.852-.385-1.249-.954-1.249-1.79 0-2.202 1.908-3.842 6.186-5.317ZM58.847 74.46c2.791-1.121 5.387-2.527 7.282-3.555 1.192-.646 2.221-1.204 2.815-1.426 1.369-.512 4.057-.926 5.448.764.785.954 1.394 3.102-.318 4.806-1.766 1.756-9.454 6.052-15.329 7.315-5.006 1.076-12.161.846-14.245-1.074-1.706-1.571-1.248-3.205-.824-4.04.705-1.384 2.178-2.36 3.46-2.36.154 0 .306.013.453.043l.018.004c2.513.502 6.717 1.338 11.24-.477Zm-.315-18.341c.452.429.936.823 1.457 1.173v2a16.472 16.472 0 0 0-2.912-.68V57.34a12.016 12.016 0 0 0 1.455-1.222Zm24.985 9.577c-.189-.708-.627-1.367-1.241-1.971.483-.232.898-.48 1.188-.734 2.323-2.04 2.147-4.933.943-6.894-1.14-1.861-5.55-2.281-7.229-1.701-.505.174-1.021.365-1.534.574a13.918 13.918 0 0 0 3.369-6.262c1.655-6.923-2.325-13.543-8.89-14.785-4.238-.801-8.51.845-11.455 3.97-3.138-3.616-7.953-5.57-12.716-4.68-3.413.639-6.28 2.612-8.074 5.559-1.77 2.907-2.282 6.42-1.442 9.89.654 2.7 2.118 5.17 4.131 7.08a70.517 70.517 0 0 0-1.724-.57c-1.523-.473-5.409-.053-6.275 1.358-.866 1.412-1.355 3.982.787 5.863.081.07.201.15.308.228-.846.748-1.462 1.585-1.712 2.512-2.251 8.343-2.001 20.57-1.871 24.12l.004.32c0 1.747 2.068 1.747 3.179 1.747H82.993c1.175 0 2.369 0 2.369-1.048v-.214c0-.21.677-14.888-1.845-24.362Z" /></g></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><defs><path id="icon_active_jumpForward_white_a" d="M0 0h69v80H0z" /></defs><g fill-rule="evenodd"><path d="M80 57.5a8.5 8.5 0 1 0 17 0 8.5 8.5 0 0 0-17 0" /><g transform="translate(30 30)"><mask id="icon_active_jumpForward_white_b" fill="#fff"><use xlink:href="#icon_active_jumpForward_white_a" /></mask><path d="m36.677 22.944-5.305-16.57S30.39.394 36.088.001c0 0 3.93-.196 4.912 3.824l5.992 22.158s3.045 2.746 1.179 7.452L37.463 57.848l28.783 13.04s3.831 1.568 2.456 6.765c0 0-2.16 3.628-6.483 1.863L26.46 63.633s-9.628 13.432-14.736 11.275c0 0-2.456-.588-3.536-3.334 0 0-6.877-10.098-8.154-15.785 0 0-.688-3.824 4.028-4.902 0 0 3.93-.687 5.5 3.432 0 0 2.85 6.764 4.324 8.921 0 0 3.045-2.647 6.974-9.02 0 0 .295-2.745 2.358-6.373l6.188-13.628L8.68 25.591s-3.34-2.255-1.67-6.373c0 0 1.08-2.745 6.68-1.764 0 0 13.655 6.568 19.647 8.137 0 0 .59-1.764 3.34-2.647" mask="url(#icon_active_jumpForward_white_b)" /></g></g></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><g fill-rule="nonzero"><path d="M94.689 62.316a7.223 7.223 0 1 0 0 14.446 7.223 7.223 0 0 0 0-14.446Z" /><path d="M51.61 49.314c8.744.423 14.502 1.569 17.275 3.437 4.16 2.802 10.158 8.242 11.434 10.006 1.276 1.763 3.354 3.734 3.354 5.543 0 1.81 2.918 10.215 5.824 10.983 2.906.767 7.7 2.535 12.275-2.613 3.05-3.431 4.952-6.221 5.705-8.37l4.547 3.113c-1.703 7.597-6.215 12.829-13.533 15.695-7.32 2.865-13.821 2.36-19.507-1.52l-11.562 2.883-17.625 1.293c-14.761-.603-25.8-4.096-33.118-10.481v-7.87c15.146 6.988 26.185 9.611 33.118 7.87-4.628-3.451-9.478-7.056-11.272-9.578-1.793-2.521-5.395-5.736-3.782-8.868 1.613-3.132 10.964-11.046 15.054-9.566 1.398 1.711 2.475 3.423 3.23 5.134-5.113 3.313-7.67 5.903-7.67 7.77 0 1.11 14.805 8.725 27.248 3.296-2.137-2.136-8.38-8.607-11.36-10.168a19.022 19.022 0 0 0-6.366-2.012l-3.268-5.977Z" /></g></svg>

After

Width:  |  Height:  |  Size: 959 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 123" fill="#fff"><g stroke-width="0" fill-rule="evenodd"><path d="M57.924 32.409c2.083 0 3.84-.727 5.284-2.17 1.444-1.444 2.17-3.202 2.17-5.285 0-2.072-.726-3.83-2.17-5.284-1.443-1.444-3.201-2.17-5.284-2.17-2.072 0-3.84.726-5.284 2.17-1.453 1.444-2.18 3.212-2.17 5.284 0 2.083.727 3.84 2.17 5.284 1.444 1.444 3.212 2.17 5.284 2.17M75.308 54.664h1.33a5.604 5.604 0 0 1 1.544-.538l.06-.01c.062-.062.128-.12.188-.184.24-.336.505-.647.783-.946a9.68 9.68 0 0 1-1.318-4.454c-.005-.124.005-.246.006-.37h-.717l-5.313-7.896c-2.73-4.017-5.972-6.02-9.733-6.02H50.853c-3.094 0-5.52.933-7.297 2.779-1.768 1.856-2.652 4.34-2.652 7.445v11.658l-5.618 7.327c-.609.814-.903 1.58-.903 2.327 0 2.004 1.306 2.996 3.928 2.996 1.198 0 2.318-.825 3.368-2.485l3.822-5.991c1.57-1.503 2.16-3.438 1.796-5.765v-9.39H48.8v53.154c0 2.662.098 4.282.324 4.871.59 1.552 1.876 2.328 3.86 2.328 1.915 0 3.163-.707 3.752-2.111.285-.806.432-2.397.432-4.764V68.788h2.014v30.27c0 4.291 1.325 6.432 3.968 6.432 1.846 0 3.054-.668 3.643-1.994.285-.667.432-2.101.432-4.321V45.059l4.096 6.394c1.404 2.141 2.73 3.211 3.987 3.211" /><path d="M76.351 55.03a3.266 3.266 0 0 1-3.266-3.265V33.649a3.266 3.266 0 0 1 6.532 0v18.116a3.266 3.266 0 0 1-3.266 3.266" /></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 123" fill="#fff"><g stroke-width="0" fill-rule="evenodd"><path d="M58.444 64.083a5.365 5.365 0 0 0-1.47 1.01 5.34 5.34 0 0 0-3.705-1.507c-1.349 0-2.601.502-3.524 1.411a4.832 4.832 0 0 0-1.432 3.473c0 3.228 2.788 5.735 7.01 9.532l.17.152 1.481 1.31.29-.256 1.19-1.05.173-.156c4.221-3.797 7.009-6.304 7.009-9.532a4.83 4.83 0 0 0-1.433-3.473 4.975 4.975 0 0 0-3.523-1.411 5.34 5.34 0 0 0-2.236.497" /><path d="m18.351 73.524 28.32-36.057s2.24-1.446 4.111.369c0 0 1.36 1.445.312 3.43l-7.569 9.666s6.889-1.843 11.169 4.96l1.105 2.098s1.02 2.155-.652 3.317c0 0-1.899 1.36-3.911-.567 0 0-1.815-4.252-5.812-3.855 0 0-4.337-.149-6.52 6.7 0 0-2.069 6.68 3.147 11.3 0 0 2.58 2.098 5.471 3.317l5.755 2.608s3.174.878 2.607 4.081c0 0-.737 2.285-3.316 2.098l-19.871-5.187-5.188 2.154-9.158-10.432ZM30.91 54.759l7.173-16.952s1.218-2.58 4.138-1.36c0 0 1.276.68 1.36 2.21L30.912 54.76ZM28.898 55.24l1.333-13.72s.765-2.465 3.628-2.012c0 0 1.02.284 1.389.907l-6.35 14.826ZM95.649 73.405l-28.32-36.057s-2.24-1.446-4.111.368c0 0-1.36 1.446-.312 3.43l7.569 9.666s-6.889-1.842-11.169 4.961l-1.105 2.098s-1.02 2.154.652 3.316c0 0 1.899 1.361 3.911-.567 0 0 1.815-4.252 5.812-3.855 0 0 4.337-.148 6.52 6.7 0 0 2.069 6.68-3.147 11.3 0 0-2.58 2.098-5.471 3.317l-5.755 2.608s-3.174.879-2.607 4.082c0 0 .737 2.284 3.316 2.098l19.871-5.188 5.188 2.155 9.158-10.432ZM83.09 54.64l-7.173-16.952s-1.218-2.58-4.138-1.36c0 0-1.276.68-1.36 2.21l12.67 16.101ZM85.102 55.121l-1.332-13.72s-.766-2.466-3.629-2.012c0 0-1.02.283-1.389.907l6.35 14.825Z" /></g></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 123" fill="#fff"><g stroke-width="0" fill-rule="evenodd"><path d="M75.866 64.525A2.83 2.83 0 0 0 72 65.56a5.66 5.66 0 0 0-2.069-7.732l-2.453-1.412a2.83 2.83 0 0 0-3.866 1.034 5.66 5.66 0 0 0-2.069-7.732l-2.453-1.412a2.83 2.83 0 0 0-3.866 1.034l4.124-7.141c1.234-2.142 1.167-4.933-.497-6.764-2.765-3.03-7.32-2.327-9.184.895l-2.911 5.04-1.843-3.19c-1.751-3.03-5.425-4.681-8.714-3.474a7.074 7.074 0 0 0-3.74 10.198l5.306 9.19a2.841 2.841 0 0 1 0 2.832l-3.668 6.353c-3.123 5.41-1.273 12.333 4.145 15.457l14.7 8.487c8.117 4.688 18.501 1.903 23.19-6.213l2.83-4.9c2.355-4.052.962-9.237-3.096-11.585M75.057 36.82c-.56.26-1.077.617-1.518 1.045a5.52 5.52 0 0 0-3.83-1.558c-1.394 0-2.688.519-3.642 1.459a4.994 4.994 0 0 0-1.48 3.589c0 3.336 2.881 5.928 7.244 9.851l.176.158 1.532 1.354.3-.265 1.228-1.086.18-.16c4.362-3.924 7.243-6.516 7.243-9.852 0-1.375-.525-2.648-1.48-3.59a5.142 5.142 0 0 0-3.642-1.458 5.52 5.52 0 0 0-2.31.514" /></g></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><g fill-rule="nonzero"><path d="M38.373 61.505C36.388 78.1 56.346 85.043 55.995 85c-9.004-1.082-24.318-10.745-22.903-23.249 1.76-15.535 16.445-17.851 17.07-17.748.625.103-9.792.827-11.789 17.502Z" /><path d="M44.691 62.272c-1.405 14.785 14.816 22.57 14.297 22.725-.52.156-21.667-6.684-19.881-23.15C40.903 45.31 52.388 44.746 53.01 45.052c.62.304-6.91 2.38-8.319 17.221Z" /><path d="M53.072 62.036C51.522 76.31 62.378 83.946 61.99 84c-.386.053-16.433-7.322-14.885-22.013C48.66 47.24 56.58 45.87 57.247 46.009c.67.137-2.616 1.695-4.175 16.027Z" /><path d="M59.896 62.361C58.031 76.651 63.354 83.984 62.981 84c-.373.017-10.376-7.522-8.816-21.632 1.566-14.164 5.029-15.435 5.684-15.365.657.068 1.918 1.006.047 15.358Z" /><path d="M66.989 62.182C66.784 77.08 65.594 84.018 65.293 84c-.305-.018-4.409-7.218-3-21.731C63.71 47.694 63.246 47.022 63.9 47c.616-.021 3.295.244 3.09 15.18Z" /><path d="M72.917 62.274c.938 14.674-6.396 21.779-6.753 21.726-.354-.052 1.408-7.057 1.504-21.723.096-14.698-2.22-15.163-1.544-15.269.67-.105 5.854.577 6.793 15.266Z" /><path d="M80.926 62.094C82.121 77.111 68.395 84.077 68.012 84c-.382-.078 8.302-7.298 7.057-22.008-1.25-14.724-6.234-15.789-5.495-15.975.737-.187 10.16 1.048 11.352 16.078Z" /><path d="M87.998 62.435c.217 15.537-17.324 22.722-17.975 22.562-.651-.158 12.59-7.426 11.572-22.562-1.018-15.154-9.502-17.062-8.52-17.403.983-.343 14.706 1.837 14.923 17.403Z" /><path d="M95.978 62.207c-.74 16.756-22.346 23.958-22.962 23.79-.616-.167 16.713-7.694 16.633-23.734-.08-16.076-13.063-18.884-12.102-19.235.96-.351 19.174 2.367 18.43 19.179ZM68.996 42.763c.061-.198.12-.398.12-.617 0-1.14-.898-2.065-2.006-2.065h-2.224V12h-.77v28.08h-2.224c-1.11 0-2.008.925-2.008 2.066 0 .219.06.42.121.617-2.979.291-5.005.86-5.005 1.516C55 45.23 59.254 46 64.5 46c5.248 0 9.5-.77 9.5-1.72 0-.656-2.026-1.226-5.004-1.517ZM71 86.244C71 85.557 67.865 85 64 85c-3.867 0-7 .557-7 1.244 0 .474 1.492.886 3.688 1.096-.046.142-.09.288-.09.446 0 .824.663 1.493 1.479 1.493h1.639v7.208c-.98.144-1.744.96-1.744 1.99 0 1.028.764 1.844 1.744 1.988v1.233c-.877.144-1.56.878-1.56 1.804v23.637c0 1.027.826 1.861 1.844 1.861a1.853 1.853 0 0 0 1.844-1.861v-23.637c0-.927-.682-1.662-1.561-1.804v-1.233c.98-.143 1.745-.96 1.745-1.988 0-1.03-.764-1.846-1.745-1.991v-7.207h1.638c.817 0 1.48-.669 1.48-1.493 0-.158-.044-.304-.09-.446 2.197-.21 3.689-.622 3.689-1.096Z" /></g></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><defs><path id="icon_active_pounceForward_white_a" d="M0 0h104v28H0z" /></defs><g fill-rule="evenodd"><path d="M84 69.5a8.5 8.5 0 1 0 17 0 8.5 8.5 0 0 0-17 0" /><g transform="matrix(-1 0 0 1 119 62)"><mask id="icon_active_pounceForward_white_b" fill="#fff"><use xlink:href="#icon_active_pounceForward_white_a" /></mask><path d="M27.53 18.992s2.03-5.544 11.332-5.197l27.062 1.386L97.552.282s4.227-1.212 5.919 1.387c0 0 2.03 3.81-1.86 6.236L69.813 25.92s-3.383 2.08-7.104 2.08l-45.074-1.387L.977 18.3s-2.198-2.361 0-4.3c0 0 1.015-1.417 3.552-.724l23.002 5.717Z" mask="url(#icon_active_pounceForward_white_b)" /></g><path d="m42 70-9.365-3.692-1.522-9.295s-1.054-5.494 3.278-5.994c0 0 3.395-.5 4.097 3.621L42 70ZM14 79.5a1.5 1.5 0 1 0 3 0 1.5 1.5 0 0 0-3 0M16 84.5a1.5 1.5 0 1 0 3 0 1.5 1.5 0 0 0-3 0M22 85a1 1 0 1 0 2 0 1 1 0 0 0-2 0M9 84a1 1 0 1 0 2 0 1 1 0 0 0-2 0M21 81.5a.5.5 0 1 0 1 0 .5.5 0 0 0-1 0" /></g></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 123" fill="#fff"><path d="M69.481 84.336 56.998 71.843 44.496 84.33a5.882 5.882 0 0 1-8.317-8.317l12.488-12.499-12.49-12.484a5.895 5.895 0 0 1 8.337-8.335l12.484 12.489 12.498-12.489a5.882 5.882 0 0 1 8.32 8.318l-12.49 12.501 12.493 12.494a5.897 5.897 0 0 1-8.338 8.34v-.013Z" fill-rule="nonzero" stroke-width="0" /></svg>

After

Width:  |  Height:  |  Size: 422 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><defs><path id="icon_active_rollOver_white_a" d="M0 0h26v26H0z" /><path id="icon_active_rollOver_white_c" d="M0 0h54v49H0z" /></defs><g fill-rule="evenodd"><g transform="translate(19 34)"><mask id="icon_active_rollOver_white_b" fill="#fff"><use xlink:href="#icon_active_rollOver_white_a" /></mask><path d="M26 13c0 7.18-5.82 13-13 13S0 20.18 0 13 5.82 0 13 0s13 5.82 13 13" mask="url(#icon_active_rollOver_white_b)" /></g><g transform="translate(25 57)"><mask id="icon_active_rollOver_white_d" fill="#fff"><use xlink:href="#icon_active_rollOver_white_c" /></mask><path d="M.227 10.976s1.638-9.117 12.286-5.387L22.75 9.733s4.71 2.486 7.167-.207L45.48.2S52.362-1.755 54 6.532c0 0-.534.015-2.582-1.19 0 0-3.48-2.033-6.552 1.698L32.989 18.227s-3.276 3.937-11.671 1.451c0 0 4.914 4.973 4.914 7.252s-2.253 21.548-2.253 21.548-6.142 2.901-13.31-4.972c0 0-12.49-14.504-10.442-32.53" mask="url(#icon_active_rollOver_white_d)" /></g><path d="m51.276 84.114 5.794-23.87s3.517-6.67 10.139-3.286l19.657 11.033 15.726 9.715s6.208 6.878-7.448 5.093l-24.831-9.388s-1.448-.872-2.483 1.608l-.621 17.168s.207 16.526-18.209 13.425l2.276-21.498Z" /><path d="M91 56.09s9.93-1.62 12 8.91M91 50.14s14.896-2.52 18 13.86" stroke="currentColor" stroke-width="3" stroke-linecap="round" /></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><g fill-rule="nonzero"><path d="m86.14 73.708-14.818-14.79s-.552-.297-.934 0c-1.995 2.034-3.608 3.644-4.925 4.788-1.316 1.145-3.141 1.865-4.67 2.035-1.528.127-3.608.423-5.774-1.865-.17-.17-.254-.424-.254-.678 0-.255.085-.466.254-.636l9.977-9.917c-.849-.805-1.4-1.356-1.655-1.568-2.378-2.076-5.222-3.432-7.472-4.577l-.425-.212c-.34-.211-.722-.38-1.104-.55l-.382-.17-.042-.042C52.684 44.89 50.944 44 49.288 44c-1.189 0-2.208.466-3.142 1.356L32.348 59.214c-2.547 2.543-2.42 4.662-2.292 5.89.042.213.042.382.042.467.085 1.017 1.061 2.33 2.675 4.238.297.339.637.72.891 1.06.934 1.144 1.614 2.288 2.293 3.39l.297.508c1.146 1.865 2.463 4.026 5.137 6.654l17.28 17.333c1.188 1.187 2.76 1.992 4.372 2.204.213.042.51.042.765.042 1.316 0 2.505-.466 3.311-1.314l.382-.381c1.529-1.526-.594-3.772-.594-3.772l-7.048-7.035a1 1 0 0 1-.297-.678c0-.254.085-.508.297-.678.17-.17.425-.297.68-.297.254 0 .51.085.679.297l7.048 7.035s2.547 2.246 4.542.975c.425-.382.85-.763 1.316-1.187 1.614-1.822-.297-4.153-.297-4.153l-7.642-7.628a1 1 0 0 1-.297-.678c0-.255.085-.509.297-.678.17-.17.425-.297.68-.297.254 0 .509.085.679.297l8.066 8.01s1.953 2.076 3.779 1.143l.51-.423 1.528-1.357s.085-.127.17-.211c1.528-2.035-.255-4.238-.255-4.238l-8.406-8.391a1 1 0 0 1-.298-.678c0-.255.085-.509.298-.679.17-.17.424-.296.679-.296.255 0 .51.085.68.296l6.707 6.696s2.59 2.458 5.392.678c.68-.678 1.231-1.229 1.443-2.33.51-2.035-.212-3.857-1.698-5.34Z" /><path d="M95.732 58.752 79.605 42.957c-3.656-3.58-7.655-3.917-10.666-.969l-6.236 6.108L51.651 58.92c-.86.8-.86 2.148-.043 2.99 2.666 2.317 5.934 3.117 5.934 3.117-.903-.252-1.849-.758-2.795-1.727-.172-.168-.258-.42-.258-.674 0-.252.086-.463.258-.631l1.72-1.685 9.29-9.098 6.322-6.192c.774-.758 1.333-.758 1.72-.716.516.084 1.42.421 2.71 1.685l16.127 15.795c1.505 1.474 2.881 3.58 1.118 5.307l-3.312 3.244a2.114 2.114 0 0 0-.645 1.516c0 .57.233 1.115.645 1.516.43.421.99.632 1.549.632.559 0 1.118-.21 1.548-.632l3.311-3.243c3.226-3.16 2.796-7.54-1.118-11.373Z" /></g></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 124" fill="#fff"><path d="M77.215 58.196a5.432 5.432 0 0 0-1.75-.653c-.38-2.41-1.78-4.63-4.027-5.937l-2.388-1.39a5.432 5.432 0 0 0-1.749-.654c-.38-2.41-1.781-4.63-4.027-5.937l-2.388-1.39 1.258-2.208c1.853-3.246 1.51-7.335-.839-9.934-1.852-2.05-4.434-3.057-7.086-2.763a8.271 8.271 0 0 0-6.267 4.134l-.484.85c-2.595-3.567-7.216-5.14-11.179-3.671a9.706 9.706 0 0 0-5.79 5.852 9.826 9.826 0 0 0 .717 8.203l5.163 9.052-3.569 6.257c-3.795 6.642-1.53 15.185 5.04 19.025l14.31 8.36c9.203 5.375 21.014 2.175 26.333-7.139l2.755-4.826c3.034-5.323 1.227-12.155-4.033-15.23Zm-41.23-18.3a4.219 4.219 0 0 1-.31-3.514 4.162 4.162 0 0 1 2.512-2.52c1.084-.406 2.31-.236 3.356.378a4.962 4.962 0 0 1 1.787 1.822l.994 1.75-4.77 8.36-3.568-6.276Zm37.738 35.568c-3.795 6.656-12.237 8.942-18.807 5.101l-14.31-8.36c-3.943-2.305-5.298-7.425-3.026-11.422l2.04-3.573.006-.007 9.636-16.895c.09-.164.116-.347.174-.516.174-.177.336-.373.458-.595l2.834-4.963a2.737 2.737 0 0 1 2.09-1.378c.524-.06 1.537-.007 2.428.98.735.816.793 2.24.135 3.389l-4.014 7.034-4.131 7.243a2.805 2.805 0 0 0 1.007 3.807 2.737 2.737 0 0 0 3.763-1.018l4.13-7.237 2.388 1.391a2.805 2.805 0 0 1 1.007 3.808v.006l-3.466 6.068a2.805 2.805 0 0 0 1.007 3.807 2.737 2.737 0 0 0 3.763-1.018l3.466-6.074 2.381 1.39a2.805 2.805 0 0 1 1.007 3.808c0 .007-.006.007-.006.013v.007l-3.453 6.054a2.805 2.805 0 0 0 1.007 3.808 2.737 2.737 0 0 0 3.762-1.019l3.466-6.074c2.627 1.535 3.53 4.95 2.014 7.609l-2.756 4.826Z" fill-rule="nonzero" /></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 123" fill="#fff"><g stroke-width="0" fill-rule="evenodd"><path d="m44.74 43.727-11.523-30.4 2.886-1.093 11.524 30.399-2.886 1.094ZM21.5 53.571l-11.524-30.4 2.886-1.093 11.522 30.4L21.5 53.57Zm-2.596-49.57c6.035-2.287 12.58-.006 15.638 6.467L11.481 19.21c-2.002-6.873 1.386-12.92 7.423-15.208Zm40.59 30.093-8.485-22.378c-1.458-3.846-5.803-5.29-9.545-3.872l-4.036 1.53C33.8 1.423 25.407-1.854 17.78 1.038c-7.627 2.89-11.74 10.908-9.184 19.268L4.54 21.84C.8 23.259-.945 27.01.513 30.858l8.483 22.379c1.458 3.846 5.574 6.353 9.317 4.935l36.923-13.998c3.742-1.418 5.716-6.233 4.258-10.08Z" mask="url(#mask-3)" transform="translate(28.842 42.364)" /><path d="M59.883 29.256a6.4 6.4 0 0 1 .33 1.368L57.009 44.48l-.022.02c-.25.389-.573.723-.954.984-.865.614-1.492.258-2.427-.226-1.417-.744-1.025-1.245-.66-2.738.729-3.087 1.02-4.172 1.527-6.525l-4.196-.508-4.507 7.316c.169.944.519 1.86.992 4.106l.33 1.37c.253 1.445 1.148 3.112.693 4.019a3.15 3.15 0 0 1-2.677 1.761l-.046.011a2.38 2.38 0 0 1-2.046-1.2l-.021-.039-.014-.055c-1.15-3.98-2.22-7.402-3.346-11.388a3.297 3.297 0 0 1 .317-2.337c.352-.694 2.687-5.328 4.39-8.723 1.072-2.156 1.693-3.378 1.827-3.67.308-.625 1.13-1.534 1.112-1.602l-.66-2.738c-.15-.616-.12-.434-.33-1.37a12.205 12.205 0 0 0-.331-1.368l10.95-2.642c.959 3.97 1.624 6.517 2.974 12.319Z" /></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><defs><path id="icon_active_sitDown_white_a" d="M0 0h21v21H0z" /><path id="icon_active_sitDown_white_c" d="M0 0h56v66H0z" /></defs><g fill-rule="evenodd"><g transform="translate(57 23)"><mask id="icon_active_sitDown_white_b" fill="#fff"><use xlink:href="#icon_active_sitDown_white_a" /></mask><path d="M21 10.5C21 16.299 16.299 21 10.5 21 4.7 21 0 16.299 0 10.5 0 4.7 4.7 0 10.5 0 16.299 0 21 4.7 21 10.5" mask="url(#icon_active_sitDown_white_b)" /></g><g transform="translate(39 47)"><mask id="icon_active_sitDown_white_d" fill="#fff"><use xlink:href="#icon_active_sitDown_white_c" /></mask><path d="M18.192 0h20.375c.384 0 .751.14 1.037.396.784.7 2.496 2.319 4.534 4.786 6.962 8.43 10.988 18.893 11.709 29.795l.145 2.195s.362 3.695-3.703 3.623c0 0-3.485.145-3.412-4.638 0 0-2.832-17.68-7.914-22.68v14.275l-2 .055c-.503.014-.986.285-1.195.742-.181.399-.145.87.726 1.16h6.535s1.307-.073 1.452 1.376v7.753l.89.382a2.478 2.478 0 0 1 1.462 2.739l-3.72 19.674c-.589 3.118-3.75 5.045-6.798 4.145a5.419 5.419 0 0 1-3.814-6.096l3.122-18.525H19.69l3.656 17.9a5.444 5.444 0 0 1-3.755 6.296c-2.979.907-6.108-.876-6.839-3.897L7.928 41.519s-.29-2.173 1.453-2.898l.217-7.464s-.145-1.666 1.96-1.666h6.68s2.158-.686.436-1.812h-3.195V13.622s-4.646 3.333-7.986 22.1c0 0-.218 5.218-3.703 5.145 0 0-4.066.29-3.775-5.145 0 0 2.975-26.869 17.046-35.415C17.4.1 17.795 0 18.192 0" mask="url(#icon_active_sitDown_white_d)" /></g><path d="M89.927 90H92v19h-6zM61 90h14l-3.365 19H64.42zM44.739 90 49 109h-6V90z" /></g></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><path d="M64.22 48.105c-4.418 0-8-3.472-8-7.756 0-4.283 3.582-7.755 8-7.755 4.42 0 8.002 3.472 8.002 7.755 0 4.284-3.582 7.756-8.001 7.756Zm-26.066-8.3 6.03-3.333c4.226 8.173 7.976 13.144 11.248 14.912 4.908 2.652 11.022 4.986 17.574 0 4.369-3.324 7.91-8.295 10.625-14.912l6.53 3.332c-1.433 5.217-4.009 10.132-7.727 14.746-5.577 6.92-6.093 6.72-7.102 8.407-1.01 1.688-1.505 6.872 0 10.333 1.003 2.308 5.111 13.783 12.324 34.424h-9.472c-6.654-14.86-10.655-22.85-12.003-23.968-2.024-1.68-3.192-1.692-4.212 0-1.019 1.692-10.358 18.273-11.443 23.968H40.478l12.365-35.621c1.753-4.105 1.527-7.44-.678-10.004-3.308-3.846-6.65-7.48-7.98-9.98-.573-1.076-2.2-2.933-3.707-6.035-.553-1.137-1.327-3.227-2.324-6.27Z" fill-rule="nonzero" stroke-width="0" /></svg>

After

Width:  |  Height:  |  Size: 865 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><defs><path id="icon_active_turnOver_white_a" d="M0 0h28v29H0z" /></defs><g fill-rule="evenodd"><path d="M79 61.5C79 67.3 74.299 72 68.5 72S58 67.3 58 61.5 62.701 51 68.5 51 79 55.7 79 61.5" /><path d="M56.865 65.43c1.265 3.842 5.068 10.235 16.252 8.55a7.057 7.057 0 0 1 4.6.853l4.722 2.742c3.412 1.984 4.578 6.417 2.593 9.867-1.915 3.328-6.072 4.526-9.42 2.715L39.749 70.748c-3.503-1.895-4.785-6.352-2.837-9.867l.12-.219c1.84-3.324 5.914-4.607 9.28-2.922l6.967 3.489c1.706.853 2.984 2.367 3.587 4.2" /><g transform="translate(82 74)"><mask id="icon_active_turnOver_white_b" fill="#fff"><use xlink:href="#icon_active_turnOver_white_a" /></mask><path d="m16.373 11.582-4.627 2.074L13.17 0 28 6.09l-4.272 2.075s3.564 5.69.177 13.27c-2.059 4.607-6.53 7.702-11.458 7.56C8.724 28.89 4.137 27.423 0 22.318c0 0 20.17 6.589 16.373-10.736" mask="url(#icon_active_turnOver_white_b)" /></g><path d="m32.51 49.623.88 4.964L44 46.123 30.533 37l.678 4.668s-6.768.061-11.192 6.925c-2.686 4.17-2.748 9.55.153 13.584 2.191 3.049 6.005 6.093 12.61 6.823 0 0-16.898-13.311-.272-19.377" /></g></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 123" fill="#fff"><defs><path id="icon_active_turnWave_white_a" d="M0 0h56.658v94.002H0z" /></defs><g stroke-width="0" fill-rule="evenodd"><path d="M76.456 48.764v2.957c2.522 1.729 3.86 4.089 3.86 6.695 0 3.379-2.242 6.343-6.433 8.054v-4.785l-5.135 5.777 5.135 5.779v-4.012c5.782-2.093 9.004-6.23 9.004-10.88-.003-3.908-2.283-7.366-6.43-9.585M76.453 48.762s.002 0 .003.002v-.002h-.003ZM57.59 34.963a7.734 7.734 0 1 0 0-15.469 7.734 7.734 0 0 0 0 15.47" /><g transform="translate(29 14)"><mask id="icon_active_turnWave_white_b" fill="#fff"><use xlink:href="#icon_active_turnWave_white_a" /></mask><path d="M54.918.822s-3.366-2.482-4.997 1.18L49.14 5.48l-.855-1.869s-.32-.666-1.134-.414c0 0-.8.267-.485 1.156 0 0 1.29 3.925 1.747 4.456 0 0-.345.019-.826 1.327 0 0-3.271 7.386-6.74 11.035 0 0-1.502 2.41-6.222 1.337 0 0-9.497-1.982-16.865 1.596 0 0-3.846 1.872-5.772 7.927 0 0-1.487 4.073-4.074 14.147l-1.495 5.826c-2.586-1.731-4.081-4.091-4.081-6.698 0-2.799 1.728-5.311 4.678-7.066v-2.817C2.648 37.622 0 41.192 0 45.235c0 3.647 2.156 6.976 5.78 9.257l-.012.053s-1.267 3.467 2.31 4.513c0 0 2.802 1.039 4.127-2.034 1.658.351 3.443.544 5.336.544V54.91c-1.626 0-3.154-.144-4.574-.403l5.13-18.513s.606-.77.66.496v54.715s-.33 2.807 1.928 2.752h4.073s2.532.496 2.422-2.532V60.82s.12-1.486 1.437-1.486c0 0 1.206 0 1.26 1.486v30.991s-.275 2.152 1.707 2.176h5.009s1.872.356 1.817-2.342V41.334s-.726-5.953 2.967-9.252c0 0 10.809-11.069 12.37-20.01 0 0 .653-1.757.144-2.059 0 0 2.529-3.022 2.758-5.915 0 0 .245-2.104-1.731-3.276" mask="url(#icon_active_turnWave_white_b)" /></g></g></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 123" fill="#fff"><g stroke-width="0" fill-rule="evenodd"><path d="M85.12 95.507c-1.515 1.661-5.263 1.91-6.596 1.867-5.748-.15-9.11-5.374-9.183-5.489-.118-.17-.238-.325-.357-.487 2.968-.602 7.137-1.769 9.074-3.773.849.742 1.762 1.35 2.739 1.748 4.252 1.74 4.87 3.602 4.923 3.75l.013.11c.12.962-.074 1.684-.612 2.274m-24.655-9.895c-3.225-.944-6.087 2.216-7.398 3.987-3.815-1.079-7.394-2.696-8.283-3.73l.922-1.638c1.45.084 3.902.19 6.965.19 5.824 0 13.837-.388 21.264-2.058a27.01 27.01 0 0 0 1.828 2.76c-.006.008-.004.01-.011.02-1.056 1.457-6.002 2.766-9.465 3.302-2.638-2.266-5.012-2.74-5.822-2.833M44.063 96.554c-.825.087-2.284.125-2.798-.583-.68-.936-.151-3.283.429-4.62l1.42-2.519c2.055 1.687 5.37 2.934 7.974 3.719-2.778 3.314-7.035 4.002-7.025 4.002m-3.104-23.819c.065-1.4.21-2.837.432-4.273.278-1.8.973-3.48 2.008-4.861 1.054-1.405 1.616-1.467 1.61-1.47.002.002.129.045.348.327.247.32.435.71.558 1.16.245.904.677 2.58 1.079 4.586-1.827 1.105-2.25 2.35-2.25 3.267v.033c0 .17.019.335.046.496.005.03.013.058.019.087.028.136.065.269.11.398.017.044.032.09.05.134.043.104.094.205.148.303.473.885 1.394 1.654 2.777 2.295.092 3.242-.526 4.708-1.182 4.988l-.226.117a3.941 3.941 0 0 1-1.535.475l-.391-.028-.002.03a2.339 2.339 0 0 1-.34-.041c-1.445-.309-2.28-2.103-2.723-3.555-.428-1.39-.608-2.894-.536-4.468m14.147-42.631L58.37 46.37c.346 1.723 2.83 1.66 3.088-.078l2.388-16.108c8.153 2.15 14.219 9.951 14.219 19.247 0 6.89-3.403 13.31-8.923 16.916-2.496-.493-5.396-.753-8.44-.753-3.94 0-7.607.431-10.433 1.222a.174.174 0 0 0-.018-.011 75.613 75.613 0 0 0-1.074-4.532 6.632 6.632 0 0 0-1.151-2.348c-1.023-1.316-2.16-1.612-2.931-1.63-.76-.028-1.48.21-2.207.671-1.51-2.911-2.3-6.17-2.3-9.535 0-9.408 6.214-17.291 14.518-19.328m5.596 43.77c-6.62 0-10.656-1.246-12.072-2.099a2.965 2.965 0 0 1-.38-.276c.928-.827 5.104-2.365 12.452-2.365 7.365 0 11.544 1.546 12.458 2.37-.915.825-5.094 2.37-12.458 2.37m-9.46 3.001c2.752.657 4.575 2.714 5.515 4.087-2.388.076-4.57.078-6.41.047.516-1.057.81-2.438.895-4.133m21.079 2.379c-3.973.83-8.13 1.294-11.984 1.536.873-1.68 2.298-2.852 3.86-3.665 2.258-.162 4.68-.495 6.786-1.067.329.877.777 1.985 1.338 3.195m16.77 13.594c-.074-.787-.766-4.044-7.015-6.602-3.534-1.446-6.53-7.698-7.93-11.392 1.53-.834 2.514-1.928 2.514-3.35 0-1.734-1.467-2.981-3.608-3.862 2.642-2.205 4.752-5 6.195-8.147l.353.05a1.868 1.868 0 0 0 2.11-1.588l1.18-8.35c.142-1-.54-1.92-1.525-2.095-.794-10.14-7.81-18.45-17.02-20.705l.412-2.768a1.569 1.569 0 0 0-1.56-1.798l-7.755.037a1.568 1.568 0 0 0-1.53 1.875l.526 2.623c-8.583 2.044-15.284 9.34-16.867 18.492l-.396.023a1.869 1.869 0 0 0-1.756 1.974l.492 8.419a1.868 1.868 0 0 0 1.974 1.756l.672-.04a23.623 23.623 0 0 0 2.027 4.343 14.29 14.29 0 0 0-2.533 6.202 43.71 43.71 0 0 0-.47 4.635c-.088 1.964.14 3.853.682 5.616.97 3.167 2.505 4.689 3.923 5.4l-3.477 6.168-.067.135c-.234.517-2.223 5.14-.118 8.052 1.153 1.593 3.137 2.255 5.956 1.956.304-.044 7.445-1.138 10.776-7.53 1.06-1.697 3.185-3.893 4.319-3.504l.247.078.23.014c.135.008 3.349.26 6.462 4.772.173.28 4.325 6.818 11.905 7.016.075.004.219.008.42.008 1.517 0 6.265-.23 8.772-2.967 1.2-1.312 1.712-3.02 1.48-4.946" /><path d="M46.08 46.921c.556-.872 1.473-1.452 2.584-1.635 1.183-.193 2.426.078 3.508.768 1.083.69 1.858 1.702 2.18 2.854.306 1.084.168 2.16-.387 3.032-1.186 1.862-3.92 2.252-6.092.868-2.173-1.384-2.977-4.025-1.792-5.887m-.023 8.738a8.524 8.524 0 0 0 4.57 1.352c2.486 0 4.84-1.137 6.189-3.256 1.078-1.693 1.359-3.74.79-5.76-.55-1.955-1.834-3.657-3.62-4.793-1.784-1.135-3.867-1.58-5.872-1.25-2.072.34-3.807 1.46-4.886 3.154v.001c-2.185 3.434-.917 8.168 2.829 10.552M64.46 48.908c.324-1.152 1.1-2.166 2.181-2.854a5.173 5.173 0 0 1 2.77-.829c1.36 0 2.615.586 3.321 1.696 1.186 1.862.382 4.503-1.792 5.886-2.175 1.384-4.908.995-6.09-.867-.556-.872-.695-1.949-.39-3.032m3.725 8.103a8.523 8.523 0 0 0 4.57-1.352c3.746-2.384 5.015-7.118 2.83-10.553-2.187-3.433-7.012-4.289-10.759-1.904-1.785 1.136-3.07 2.838-3.62 4.791-.568 2.022-.287 4.068.791 5.762 1.35 2.12 3.702 3.256 6.188 3.256M30.002 69.46l2.111-4.307-2.873-1.408-4.37 8.914h3.522l-3.538 3.485 4.866 2.974-3.41 3.376 2.251 2.274 6.314-6.25-4.857-2.968 6.185-6.09z" /></g></svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 123" fill="#fff"><g stroke-width="0" fill-rule="evenodd"><path d="M81.313 20.6s-4.505-1.327-9.664-5.237c0 0-2.543-2.121-4.723 0 0 0-3.27 2.916-4.505 6.562 0 0-1.598.729-2.107 3.447v.862l-2.252 5.501 6.539-3.977s1.453-6.43 7.048-6.23c0 0 1.89-.266 3.56.927 0 0 4.505-.398 6.104 2.784 0 0 3.633-1.856 0-4.64" /><path d="M79.424 30.277s2.76-2.055 1.235-5.038c0 0-1.671-3.115-7.702-1.856 0 0-3.488 1.525-7.12 4.309l-8.356 5.17s-3.052 2.121-1.962 4.507c0 0 .58 2.254 2.543 3.182 0 0 10.463 4.772 11.117 13.588 0 0-1.017 2.983 1.38 3.447 0 0 3.197.928 3.488-2.983 0 0-.509-3.645-5.304-8.816 0 0-3.197-3.712-9.446-7.821 0 0-1.163-.53-.073-1.392 0 0 3.125-4.574 10.39-4.839 0 0 8.284-.596 9.81-1.458" /><path d="M58.062 30.289s-14.18 5.485-16.869 10.094c0 0-1.67 2.422 1.817 4.013l9.405 4.868.692-2.063-7.554-4.53s-1.599-.794-.073-1.988c0 0 5.74-2.518 9.446-3.314 0 0-1.64-2.278 1.962-4.507l1.174-2.573Z" /><path d="m56.124 39.33-7.363 19.206s-.292 1.15 1.259 1.415c0 0 3.26 0 5.263-.884 0 0 .647-.266 1.519.884 0 0 7.75 1.679 9.204 3.535 0 0 .678 1.414 1.84 0l6.782-20.063 2.81-.088.678-2.952-1.84-1.644v-1.59l-2.132-1.238s3.197-1.635 1.453-4.176l-3.1-1.447-2.883 1.447h-5.837l-3.487 2.674-1.636 1.325" /><path d="m49.197 58.906 11.77-.723s7.122.663 6.976 4.772c0 0 1.577 3.05-3.062 7.69 0 0-7.255 8.749-7.255 15.378 0 0-1.163 4.109 1.889 4.905 0 0 14.387 7.821 14.241 15.51 0 0-.145 2.387-3.487 2.122 0 0-2.471.132-2.035-3.315 0 0 3.163-7.424-11.498-12.86 0 0-5.214-2.385-3.906-9.81l3.906-12.328-13.897.53-6.721 8.882s-1.889 2.917.727 4.773c0 0 7.847 9.147 7.556 16.703 0 0-.145 1.989-2.18 1.989 0 0-2.179.265-1.598-3.049 0 0 2.47-6.894-6.975-15.378 0 0-4.505-3.05-1.599-8.087l12.498-16.208s1.762-1.318 4.65-1.496" /></g></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 124" fill="#fff"><path d="M27.963 56.812a1.75 1.75 0 1 1 3.498.001 1.75 1.75 0 0 1-3.498-.001m-2.524 2.106a3.633 3.633 0 0 1-3.124 4.078 9.773 9.773 0 0 0 3.798 2.7 3.634 3.634 0 1 1 7.196 0 9.796 9.796 0 0 0 3.787-2.681 3.634 3.634 0 1 1 2.39-6.61 9.721 9.721 0 0 0-1.628-5.218 3.636 3.636 0 0 1-6.175 1.053 3.635 3.635 0 0 1 .434-5.122 9.776 9.776 0 0 0-4.811 0 3.634 3.634 0 0 1-1.051 6.166 3.633 3.633 0 0 1-4.69-2.097 9.744 9.744 0 0 0-1.629 5.107 3.62 3.62 0 0 1 1.895-.532 3.633 3.633 0 0 1 3.608 3.156m-5.59-2.34c0-5.449 4.417-9.865 9.866-9.864 5.448.001 9.864 4.418 9.863 9.866-.001 5.449-4.419 9.864-9.867 9.864-5.448-.001-9.864-4.419-9.863-9.867m-4.217.003c0 7.777 6.304 14.081 14.08 14.081 7.777 0 14.08-6.304 14.08-14.08 0-7.777-6.303-14.081-14.08-14.081-7.776 0-14.08 6.304-14.08 14.08" /><path d="M54.64 79.5H12.084c-.471-1.284 2.018-.448 4.813-3.351 3.38-1.426 5.551-1.308 8.668-2.783 2.013-.953 4.852-3.52 10.566-9.273 1.223-1.23 2.364-2.66 3.488-4.076l.173-.217c1.697-2.138 3.362-4.216 5.219-5.493 5.92-4.073 9.13-5.43 9.629-4.073V79.5ZM60.745 43.87c.499-1.688 5.964-2.595 11.885 2.466 1.857 1.587 3.46 5.018 5.157 7.674l.897 1.645c1.124 1.758 1.365 3.17 2.588 4.7 5.714 7.148 8.553 10.339 10.566 11.522 3.117 1.834 3.873 2.712 7.252 4.484 2.253 1.181 2.243 2.466 5.382 3.139H62.763l-2.018-35.63Z" /><path d="m50.205 50.995 8.97-6.577s3.41-2.493 5.18-1.795c1.772.698 4.463 13.854 4.463 14.128 0 .274-.897 12.334-.897 13.43 0 1.097-1.066 9.32-1.066 9.32H51.154" /></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 124" fill="#fff"><path d="M72.017 57.055a6.534 6.534 0 0 0 2.16-5.204l4.222 2.835 7.88-8.634a3.899 3.899 0 0 1 5.513-.252 3.902 3.902 0 0 1 .251 5.512L81.897 62.431a3.897 3.897 0 0 1-5.057.606l-6.581-4.42 1.758-1.562Zm4.354-8.799c-5.026 0-9.103-4.077-9.103-9.108 0-5.023 4.077-9.101 9.103-9.101 5.029 0 9.106 4.078 9.106 9.101 0 5.03-4.077 9.108-9.106 9.108ZM19.633 90.218l18.359-10.606 6.424-9.91a6.476 6.476 0 0 0 3.24 3.626l2.82 1.375-6.446 9.942a3.913 3.913 0 0 1-1.32 1.257L23.536 96.976a3.9 3.9 0 0 1-5.33-1.425c0-.001 0-.002-.002-.003a3.902 3.902 0 0 1 1.428-5.33Z" /><path d="m63.925 78.358-15.128-7.367a3.904 3.904 0 0 1-.884-6.424l13.4-11.919-7.993-5.296-11.694 3.345A3.91 3.91 0 0 1 36.8 48.02a3.907 3.907 0 0 1 2.677-4.827l13.426-3.837a3.9 3.9 0 0 1 3.228.497l13.72 9.092a3.886 3.886 0 0 1 1.735 2.977 3.893 3.893 0 0 1-1.299 3.19L57.453 66.524l13.767 6.707a3.897 3.897 0 0 1 2.039 2.422 3.896 3.896 0 0 1-.428 3.135L62.42 95.646a3.902 3.902 0 0 1-6.64-4.1l8.144-13.188ZM83.825 78.545l-3.915.28 13.664-9.14-6.253 7.143 4.695-.3-14.424 9.14zM85.477 67.846l7.586-6.029 3.255 1.97zM21.156 65.197l11.08-7.417 5.342 1.225zM45.754 34.81l11.714-9.31 5.027 3.04zM20.584 75.319l7.088-5.633 3.041 1.84z" /></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><path d="m73.765 100.203 4.253.001h2.687l.493.002h1.385l.427.002h1.17l.351.001h.927l.263.002h.46l.193.001h.313l.116.002.154.001c.065 0 .057.002-.033.004A4.788 4.788 0 0 1 82.132 105H47.869a4.789 4.789 0 0 1-4.785-4.5l-.008-.283c-.09 0-.1 0-.032-.002h.153l.116-.001h.313l.193-.002h.459l.266-.001h.925l.35-.002 1.171-.001h1.812l.494-.002h2.12l.567-.002h4.252l.644-.001h16.886Zm-7.384-51.366 2.746.003c9.805.011 18.475.063 17.84.157l-.043.005v49.465c.957.094-7.296.147-16.899.161l-1.052.002h-.528l-1.374.002h-5.515l-.53-.002h-1.053c-9.428-.016-17.554-.067-16.94-.157l.043-.006V49.002c-.987-.097 7.818-.15 17.798-.162l2.743-.003h2.766-.002Zm8.048 9.629L55.993 72.474l6.31 1.72-6.731 13.158 18.435-14.009-6.309-1.72 6.73-13.157Zm7.703-16.047a4.788 4.788 0 0 1 4.792 4.783c.09 0 .098 0 .032.001h-.153l-.116.002h-.313l-.193.001h-.46l-.264.002-.928.001h-.348l-1.171.002h-1.812l-.495.002h-2.121l-.564.001-4.253.002H56.233l-4.252-.002h-2.684l-.495-.002h-1.384l-.428-.001h-1.17l-.35-.002h-.928l-.264-.001h-.459l-.193-.002h-.313l-.116-.001-.154-.002c-.066 0-.057-.001.033-.003l.008-.28a4.789 4.789 0 0 1 4.785-4.501h34.263ZM68.127 36a1.573 1.573 0 0 1 1.572 1.568v3.23c.141.007-.673.01-1.877.013h-.691l-.368.002h-4.585c-.626.004-1.251-.001-1.876-.016v-3.229A1.567 1.567 0 0 1 61.873 36h6.254Z" fill-rule="nonzero" /></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 123.2" fill="#fff"><g fill-rule="evenodd"><path d="m75.891 70-.053 8.157H56.785l-.48 8.157H37.679v8.529H28V103h18.325v-8.58h19.053v-8.106h18.199v-8.58H99V70zM50.67 32.396c3.046 0 5.566-2.527 5.566-5.698 0-3.118-2.468-5.698-5.565-5.698-3.045 0-5.566 2.58-5.566 5.698 0 3.118 2.52 5.698 5.566 5.698" /><path d="m56.604 57.124.105-7.848 6.72 3.601c2.206.753 4.988-1.075 2.626-3.978l-9.241-5.268v-4.784c.21-6.129-11.814-7.311-11.814 0v17.524c.998 2.15 1.995 3.01 2.993 3.333l-.053 23.33c.21 1.936 3.308 5.377 5.986.377l-.262-20.535 7.193 3.064c.105 2.634-.21 5.698 0 8.816.84 2.527 4.515 3.602 6.143.108V62.822l-10.396-5.698Z" /></g></svg>

After

Width:  |  Height:  |  Size: 735 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><g fill-rule="nonzero"><path d="M51.967 31.281a5.779 5.779 0 1 0 11.557 0 5.779 5.779 0 0 0-11.557 0ZM41.788 71.299l2.849-3.476c-1.9-4.01-2.85-6.752-2.85-8.225 0-2.21 1.33-7.463 4.868-8.57v15.085l-4.867 37.4h5.899l7.837-28.168 8.798.696c2.536.336 3.804 1.173 3.804 2.512-.337 1.72-1.783 6.24-4.337 13.563h6.31l5.38-11.677c2.238-5.867 1.045-10.072-3.578-12.616-4.623-2.544-8.604-3.54-11.943-2.986l.745-9.04c2.432 3.305 4.907 5.327 7.423 6.066 2.517.74 6.282 1.56 11.296 2.458l1.438-3.438c-7.904-3.127-12.352-5.65-13.347-7.57-1.49-2.881-3.19-8.847-3.19-9.789 0-.941-7.535-5.303-16.636-2.822-5.563 1.517-7.807 4.656-9.089 6.432-1.282 1.775-5.584 5.62-3.319 14.73 1.323 3.973 3.492 7.118 6.509 9.435Z" /><path d="M101.084 74.216H82.672V95.72H63.578v19.35h-17.39v-5.375h12.275v-19.35h19.095V68.84h23.526z" /></g></svg>

After

Width:  |  Height:  |  Size: 930 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 123.2" fill="#fff"><g fill-rule="nonzero"><path d="M88.469 46.296 92.73 48.6c.915.494 1.802 1.555 1.17 2.496-.539.814-1.609.695-2.487.22l-5.002-2.706 2.058-2.314Zm-3.155 15.48c1.06 5.019 2.158 10.77 3.329 15.752L99.296 90.1a2.96 2.96 0 0 1-.348 4.179 2.96 2.96 0 0 1-4.18-.347L83.651 80.828a2.925 2.925 0 0 1-.622-1.235l-3.74-11.254 6.026-6.565ZM76.04 43.918a5.164 5.164 0 0 0 2.625 3.118c1.198.603 6.593 1.81 6.593 1.81a30.81 30.81 0 0 1-.366 10.752l-6.172 6.556-8.99 9.828-1.088 16.247A2.973 2.973 0 0 1 65.69 95c-.064 0-.137 0-.201-.01a2.966 2.966 0 0 1-2.762-3.153l1.152-17.353a2.98 2.98 0 0 1 .841-1.884l9.31-12.132c-.53-1.436-2.332-8.32-2.753-9.911l4.764-6.638Zm-16.615-5.943 5.24 1.381c.228.055.438.155.64.274l5.733 4.681c.512.22 2.524.33 3.091.421l-3.85 4.827-7.078-5.988-3.996-1.298.22-4.298Zm-7.974-2.97c1.81.09 6.584 3.473 6.584 3.473l-.156 3.045s-4.837 2.88-6.913 2.77c-2.323-.119-4.097-2.295-3.96-4.864.128-2.56 2.122-4.544 4.445-4.425" /><path d="M84.524 38.167c1.26 1.286 2.476 6.96 2.476 6.96l-2.166 2.108s-5.409-.921-6.843-2.39c-1.618-1.651-1.517-4.653.31-6.432 1.819-1.779 4.605-1.897 6.223-.246M19.173 46.25l4.249-2.3 2.86 3.504-4.998 2.7a2.2 2.2 0 0 1-1.06.265c-.786 0-1.554-.42-1.956-1.168a2.208 2.208 0 0 1 .905-3.001m20.906 14.816 5.739 11.039c.365.483.575 1.058.594 1.66l.11 17.151c.072 1.633-1.207 3.011-2.843 3.084h-.127a2.964 2.964 0 0 1-2.96-2.837l-.074-16.23-4.112-6.359 3.673-7.508ZM27.872 46.898c5.455-.694 7.319-1.214 8.653-3.568 1.553.794 4.404.484 5.72-.072l7.474 6.294 3.948 1.241-.448 4.516c-.037-.046-5.784-1.925-5.784-1.925l-5.309-3.85c-.192.684-1.179 3.877-1.964 6.423L35.036 66.43l-3.984 12.198-6.67 12.316a2.96 2.96 0 0 1-2.815 2.034 3.01 3.01 0 0 1-.932-.146 2.966 2.966 0 0 1-1.892-3.74l6.561-11.942c.868-3.668 3.5-14.688 3.5-14.688-.585-5.292-1.736-10-.932-15.564Zm33.753 10.756c-1.855.027-6.944-3.093-6.944-3.093l-.046-3.102s4.733-3.257 6.853-3.284c2.376-.028 4.322 2.062 4.359 4.68.046 2.618-1.855 4.762-4.222 4.799" /><path d="M28.39 36.88C26.93 38.03 25 43.617 25 43.617L26.996 46s5.818-.53 7.494-1.853c1.873-1.48 2.024-4.3.339-6.309-1.686-2.008-4.566-2.437-6.44-.958" /><path d="M39.192 32.004c-2.495.089-4.543 1.736-5.192 3.942a5.55 5.55 0 0 1 2.02 1.427c1.344 1.488 1.71 3.455 1.106 5.067a5.713 5.713 0 0 0 2.496.46c3.108-.114 5.53-2.648 5.412-5.651-.12-3.012-2.734-5.36-5.842-5.245m35.853.124c-3.09-.337-5.878 1.807-6.225 4.802-.347 2.994 1.865 5.696 4.955 6.033.84.097 1.663 0 2.422-.248-.247-1.524.302-3.216 1.609-4.456A5.722 5.722 0 0 1 80 36.965c-.265-2.49-2.285-4.545-4.955-4.837" /></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><defs><path id="icon_model_crossStep_white_a" d="M0 0h11v25H0z" /><path id="icon_model_crossStep_white_c" d="M0 0h45v81H0z" /></defs><g fill-rule="evenodd"><path d="M74 27c0 5.523-4.477 10-10 10s-10-4.477-10-10 4.477-10 10-10 10 4.477 10 10" /><g transform="translate(61 98)"><mask id="icon_model_crossStep_white_b" fill="#fff"><use xlink:href="#icon_model_crossStep_white_a" /></mask><path d="M1.281 0s3.752 5.57 5.876 11.536c0 0 2.407 6.83 3.823 7.426 0 0 .708 6.232-6.372 6.033 0 0-4.814-.795-4.601-5.9L1.28 0Z" mask="url(#icon_model_crossStep_white_b)" /></g><g transform="translate(42 37)"><mask id="icon_model_crossStep_white_d" fill="#fff"><use xlink:href="#icon_model_crossStep_white_c" /></mask><path d="m34.734 14.987-1.378 1.077a.195.195 0 0 1-.315-.151v-3.811c0-.173.225-.241.323-.099l1.496 2.172a.598.598 0 0 1-.126.812Zm-22.283.301c0 .288-.346.44-.561.246l-1.787-1.605a.33.33 0 0 1 0-.492l1.787-1.604a.336.336 0 0 1 .56.245v3.21Zm31.834-2.663c-1.45-4.23-6.622-8.94-6.622-8.94C33.937.82 29.109 0 29.109 0c-6.553 3.412-13.244 0-13.244 0C6.346 2.866 1.38 10.578 1.38 10.578c-3.105 4.436 0 7.03 0 7.03l11.174 10.509L12.21 40.5c-.275 4.671 3.656 9.456 3.656 9.456C27.73 66.812 29.73 76.23 29.73 76.23c2.621 5.665 7.243 4.71 7.243 4.71 5.311-1.434 3.035-7.917 3.035-7.917-4-12.148-7.933-17.676-7.933-17.676l.759-11.26c.552-1.98.207-15.56.207-15.56l10.692-9.896c2.483-2.047.552-6.006.552-6.006Z" mask="url(#icon_model_crossStep_white_d)" /></g></g></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><defs><path id="icon_model_damping_white_a" d="M0 0h58v61H0z" /><path id="icon_model_damping_white_c" d="M0 0h30v32H0z" /></defs><g fill-rule="evenodd"><g transform="translate(35 49)"><mask id="icon_model_damping_white_b" fill="#fff"><use xlink:href="#icon_model_damping_white_a" /></mask><path d="M53.992 0c-2.214 0-4.009 1.784-4.009 3.985v21.502H8.017V3.985C8.017 1.785 6.222 0 4.008 0 1.795 0 0 1.784 0 3.985v37.462c0 2.2 1.795 3.985 4.008 3.985.186 0 .367-.017.546-.041v.041H24.54v11.583c0 2.2 1.794 3.985 4.008 3.985s4.009-1.784 4.009-3.985V45.432H54.53v-.04c1.958-.263 3.47-1.927 3.47-3.945V3.985C58 1.785 56.204 0 53.99 0" mask="url(#icon_model_damping_white_b)" /></g><g transform="translate(49 30)"><mask id="icon_model_damping_white_d" fill="#fff"><use xlink:href="#icon_model_damping_white_c" /></mask><path d="M3.994 32h22.012A3.992 3.992 0 0 0 30 28.01a3.992 3.992 0 0 0-3.994-3.988h-7.012V3.989A3.992 3.992 0 0 0 15 0a3.992 3.992 0 0 0-3.994 3.99v20.033H3.994A3.992 3.992 0 0 0 0 28.01 3.992 3.992 0 0 0 3.994 32" mask="url(#icon_model_damping_white_d)" /></g></g></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 123" fill="#fff"><g fill-rule="nonzero"><path d="M64.174 24.513c.484.738.652 1.635.467 2.496a3.316 3.316 0 0 1-1.453 2.091c-3.855 2.476-5.162 9.12-2.763 15.937.084.232.137.47.165.702 2.282 6.209 6.195 10.072 12.577 13.087a3.3 3.3 0 0 1 1.505 4.384c-.784 1.624-2.733 2.333-4.393 1.599-5.625-2.659-9.87-6.03-12.904-10.62-.131.249-.24.497-.335.749-.383 1.008-1.15 2.1-1.28 5.305a9.44 9.44 0 0 1-.139 1.247 3.243 3.243 0 0 1-.036.395c-1.712 11.424 1.503 24.226 8.69 32.699a3.292 3.292 0 0 1-.44 4.64 3.377 3.377 0 0 1-4.695-.376c-3.726-4.394-6.527-9.7-8.329-15.39a3.36 3.36 0 0 1-2.178-.928l-15.27-14.59a3.292 3.292 0 0 1-.916-1.545 3.285 3.285 0 0 1-.081-3.161A3.352 3.352 0 0 1 35 61.443l8.128-.843c.005-.5.033-1 .039-1.478.075-5.14 2.335-11.206 5.388-14.094.909-.869 1.876-1.678 2.893-2.42a5.181 5.181 0 0 1 1.335-.7c-1.106-7.62 1.215-14.814 6.754-18.373a3.382 3.382 0 0 1 2.523-.462c.87.182 1.63.7 2.114 1.437v.003ZM48.449 66.715l-6.062.63 6.327 6.05a50.678 50.678 0 0 1-.265-6.68Zm19.495-34.547c3.863 0 6.983 3.086 6.983 6.908 0 3.822-3.12 6.908-6.983 6.908a7.013 7.013 0 0 1-4.94-2.02 6.863 6.863 0 0 1-2.042-4.888c0-3.822 3.161-6.908 6.982-6.908ZM87.36 61.534c-1.402-.812-2.448-2.007-2.448-2.007-3.303-3.245-5.508-.31-5.508-.31l-.139 11.12c-.209-.052-1.641-2.433-1.862-2.419-1.286.084-2.403 1.688-2.403 3.964C75 74.156 76.48 76 78.305 76c1.824 0 3.304-1.844 3.304-4.118 0-.247-.02-.486-.054-.722h.003l-.59-10.765C84.73 67.595 90 63.388 90 62.719c0-.297-1.533-.544-2.64-1.185Z" /></g></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 123" fill="#fff"><path d="M31.661 66.93h7.735a2.614 2.614 0 1 0 0-5.228H31.66a2.614 2.614 0 1 0 0 5.229Zm55.018-25.372H79.51v-1.862a6.964 6.964 0 0 0-13.926 0v6.606c0 .122.006.243.025.357a6.96 6.96 0 0 0 6.938 6.37 6.957 6.957 0 0 0 6.925-6.255h7.206a2.614 2.614 0 0 0 2.614-2.614 2.611 2.611 0 0 0-2.614-2.602Zm0 40.3H79.51V81.4a6.964 6.964 0 0 0-13.926 0v6.607c0 .12.006.242.025.357a6.964 6.964 0 0 0 13.876 0 2.19 2.19 0 0 0 .025-.357v-.931h7.168a2.614 2.614 0 0 0 2.614-2.615 2.611 2.611 0 0 0-2.614-2.602Zm-27.7 0H31.66a2.614 2.614 0 1 0 0 5.23h27.317a2.614 2.614 0 1 0 0-5.23ZM31.66 46.781h27.317a2.614 2.614 0 1 0 0-5.23H31.661a2.614 2.614 0 1 0 0 5.23Zm55.018 14.927h-27.44V61.128a6.964 6.964 0 0 0-13.926 0v6.606c0 .121.007.243.026.357a6.964 6.964 0 0 0 13.876 0 2.19 2.19 0 0 0 .025-.357v-.81h27.439a2.614 2.614 0 0 0 2.614-2.614 2.611 2.611 0 0 0-2.614-2.602Z" fill-rule="nonzero" /></svg>

After

Width:  |  Height:  |  Size: 1000 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 125" fill="#fff"><path d="m80.227 99-4.177-9.118-9.095-4.155 9.091-4.18 4.181-9.092 4.148 9.091 9.125 4.181-9.125 4.148L80.227 99Zm-33.182-9.955-8.295-18.25L20.5 62.5l18.25-8.295 8.295-18.25 8.296 18.25L73.59 62.5l-18.25 8.295-8.296 18.25Zm0-39.818-4.18 9.122-9.092 4.151 9.092 4.18 4.18 9.093 4.145-9.095 9.128-4.178-9.125-4.148-4.148-9.125Zm33.182 3.318-4.177-9.088-9.095-4.184 9.091-4.145L80.227 26l4.148 9.125 9.125 4.148-9.125 4.18-4.148 9.092Z" fill-rule="nonzero" stroke-width="0" /></svg>

After

Width:  |  Height:  |  Size: 596 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 123" fill="#fff"><path d="M73.872 99.843a3.792 3.792 0 0 1-5.2-1.185l-7.924-12.97a3.918 3.918 0 0 1-.92-1.378l-.14-.356-.515-.843.206.053-3.152-7.95a7.255 7.255 0 0 1-.956.07h-.111l-3.126 23.534c-.034 2.066-1.799 2.996-3.943 3.158a3.418 3.418 0 0 1-3.82-3.447l3.93-28.747a7.166 7.166 0 0 1-.204-1.67v-6.566l-2.984 7.442a3.299 3.299 0 0 1-4.439 1.931 3.753 3.753 0 0 1-1.981-4.707l8.75-21.832c.185-.504.486-.957.878-1.324a7.17 7.17 0 0 1 6.949-5.42h.101c.505 0 1.008.054 1.501.16a3.24 3.24 0 0 1 3.634.912L71.423 51.46c.4.463.666 1.026.773 1.628.035.027.07.047.105.077l-.777 12.395a3.36 3.36 0 1 1-6.693-.444l.64-10.187-3.028-3.503v16.687c-.002.3-.022.599-.061.896l4.71 11.888c.118.302.2.616.246.937l7.718 12.851a3.721 3.721 0 0 1-1.184 5.158ZM53.712 36.58a7.79 7.79 0 1 1 7.743-7.79 7.766 7.766 0 0 1-7.743 7.79Z" fill-rule="nonzero" /></svg>

After

Width:  |  Height:  |  Size: 942 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 124" fill="#fff"><path d="M56.514 81.138c-3.888 0-7.04 3.056-7.04 6.825 0 3.77 3.152 6.825 7.04 6.825 3.889 0 7.041-3.055 7.041-6.825s-3.152-6.825-7.04-6.825Zm-22.938 7.305 5.306 2.933c3.72-7.193 7.019-11.567 9.898-13.123 4.32-2.334 9.7-4.388 15.465 0 3.845 2.925 6.961 7.3 9.35 13.123l5.746-2.933c-1.26-4.59-3.527-8.916-6.799-12.976-4.908-6.09-5.362-5.913-6.25-7.399-.889-1.485-1.324-6.047 0-9.093.883-2.03 4.498-12.128 10.845-30.293h-8.335C62.947 41.76 59.426 48.79 58.239 49.775c-1.78 1.477-2.809 1.488-3.706 0-.897-1.49-9.116-16.081-10.07-21.093H35.62L46.5 60.03c1.543 3.613 1.344 6.547-.596 8.803-2.91 3.384-5.852 6.582-7.023 8.783-.504.946-1.935 2.58-3.261 5.31-.487 1.001-1.168 2.84-2.045 5.518Z" fill-rule="nonzero" /></svg>

After

Width:  |  Height:  |  Size: 832 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><g fill-rule="nonzero"><path d="M68.432 36.816c-.005 2.761 1.68 5.253 4.266 6.313a6.994 6.994 0 0 0 7.547-1.473 6.756 6.756 0 0 0 1.505-7.44C80.68 31.664 78.155 30 75.354 30c-3.82 0-6.917 3.05-6.922 6.816Zm27.8 32.549a19.449 19.449 0 0 1-9.734-6.14c-1.42-1.76-2.509-6.14-3.234-13.115a.874.874 0 0 0-.06-.299A5.938 5.938 0 0 0 79 46.741a27.586 27.586 0 0 0-8.343 0c-.102.028-.203.06-.302.1h-.11c-.181.07-.356.153-.525.248-1.47.765-5.34 3.388-9.985 11.496a37.579 37.579 0 0 0-4.162 14.704v.884a2.2 2.2 0 0 0 2.157 1.69 2.26 2.26 0 0 0 2.015-1.282c1.159-3.18 3.789-10.065 4.574-12.122.152-.4.385-.765.686-1.073a26.876 26.876 0 0 1 4.03-3.338v23.15c.014.049.014.1 0 .149a46.827 46.827 0 0 1-4.474 11.684c-1.783 3.338-5.199 7.62-9.068 13.224a3.725 3.725 0 0 0-.494 1.819c.117.716.439 1.384.927 1.927a3.238 3.238 0 0 0 2.53.994 2.895 2.895 0 0 0 1.984-.765c.06 0 .09-.09.151-.15 3.87-3.278 6.741-7.809 9.22-11.922a118.114 118.114 0 0 0 6.62-12.916.59.59 0 0 1 .937-.179c5.045 4.69 8.098 8.508 9.16 11.456a46.032 46.032 0 0 1 2.115 10.82c.047 1.295 1.035 2.37 2.338 2.543h1.3c1.503-.136 2.69-1.318 2.811-2.802-.12-9.002-1.29-15.122-2.952-18.49-1.753-3.636-5.64-8.346-11.658-14.128a.621.621 0 0 1-.212-.537c.423-5.246.786-7.63 1.058-7.213 3.87 6.11 10.399 7.273 14.107 7.481a2.413 2.413 0 0 0 2.509-1.877v-.09a2.45 2.45 0 0 0-1.713-2.861Z" /><path d="M40.972 94h16.056C59.01 94 60 94.833 60 96.5s-.99 2.5-2.972 2.5H40.972C38.99 99 38 98.167 38 96.5s.99-2.5 2.972-2.5ZM32.972 102h16.056c1.981 0 2.972.833 2.972 2.5s-.99 2.5-2.972 2.5H32.972c-1.981 0-2.972-.833-2.972-2.5s.99-2.5 2.972-2.5Z" /></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><g fill-rule="evenodd"><g fill-rule="nonzero"><path d="M80 49a9 9 0 1 0-9-9.011A9.011 9.011 0 0 0 80 49Z" /><path d="M95.03 47.15c-5.343 7.48-12.486 8.351-20.18 3.774-.527-.31-2.855-1.583-3.36-1.892-12.382-7.353-24.742-4.462-33.031 7.146-3.52 4.944 4.62 9.623 8.106 4.737 4.288-5.999 9.734-7.731 15.685-5.827-3.05 5.288-5.71 10.553-9.482 18.032-3.773 7.478-12.074 13.454-20.054 8.786-5.767-3.372-10.96 4.91-5.216 8.27 10.903 6.366 23.687 2.455 30.211-5.827.235.12.476.228.723.321 5.331 1.858 12.325 6.802 14.446 8.557 2.121 1.755 5.79 10.679 7.957 15.175 2.637 5.437 11.145 1.48 8.508-3.991-2.466-5.093-6.593-15.256-9.826-17.825-2.58-2.065-7.602-5.896-11.856-8.075a371.44 371.44 0 0 1 9.035-16.53c10.067 3.086 19.63-.55 26.44-10.093 3.52-4.944-4.621-9.624-8.107-4.737Z" /></g><g fill-rule="nonzero"><path d="M35.972 98h16.056c1.981 0 2.972.833 2.972 2.5s-.99 2.5-2.972 2.5H35.972c-1.981 0-2.972-.833-2.972-2.5s.99-2.5 2.972-2.5ZM27.972 106h16.056c1.981 0 2.972.833 2.972 2.5s-.99 2.5-2.972 2.5H27.972c-1.981 0-2.972-.833-2.972-2.5s.99-2.5 2.972-2.5Z" /></g></g></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 123" fill="#fff"><g stroke-width="0" fill-rule="evenodd"><path d="M22.2 50.51c0 5.445 4.414 9.86 9.858 9.86 5.445 0 9.86-4.415 9.86-9.86 0-5.444-4.415-9.858-9.86-9.858-5.444 0-9.858 4.414-9.858 9.858M71.62 45.937s3.552-.805 4.795 2.235c1.17 2.199.077 4.285-.854 4.983-.4.422-22.775 11.414-22.775 11.414l7.905 8.703L71.793 62.09c1.942-1.44 5.598-1.493 7.19.517 1.767 2.361 9.93 14.596 14.551 21.994.774 1.791-.207 5.002-2.055 6.014-2.082 1.137-4.137 1.225-5.82 0-2.042-2.51-10.784-14.09-10.784-14.09s-9.744 11.362-10.126 11.78c-3.624 3.982-9.439 4.261-13.407.621l-13.95-12.8c-3.969-3.64-5.277-9.577-1.644-13.557 3.4-3.404 25.221-11.767 35.872-16.63M57.484 40.185l-1.314-3.017-10.81 4.707a1.37 1.37 0 0 0-.71 1.803l.22.504a1.37 1.37 0 0 0 1.803.71l10.81-4.707Z" /><path d="m61.511 44.202 6.174-8.878a1.37 1.37 0 0 0-.914-2.138l-10.766-1.67a1.37 1.37 0 0 0-1.467 1.902l4.592 10.549a1.37 1.37 0 0 0 2.382.237v-.002Z" /></g></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><g fill-rule="nonzero"><path d="M63 35c-9.926 0-18 8.166-18 18.201V66h7.32V53.201c0-5.954 4.79-10.8 10.68-10.8s10.68 4.846 10.68 10.8V66H81V53.201C81 43.166 72.925 35 63 35ZM84.39 67H43.61C40.511 67 38 69.582 38 72.766v27.47c0 3.184 2.512 5.764 5.61 5.764h40.78c3.098 0 5.61-2.582 5.61-5.764v-27.47C90 69.582 87.488 67 84.39 67Zm-9.08 22.03h-5.562c-.956 2.291-3.168 3.896-5.747 3.896-2.58 0-4.793-1.605-5.75-3.896h-5.559v-5.06h5.56c.956-2.29 3.17-3.895 5.749-3.895 2.579 0 4.79 1.605 5.747 3.895h5.561v5.06Z" /></g></svg>

After

Width:  |  Height:  |  Size: 638 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><path d="M95 70.679c-.102 17.144-14.208 30.774-31.34 30.31-16.186-.435-29.226-13.487-29.65-29.673C33.559 54.148 47.254 40.042 64.429 40a30.564 30.564 0 0 1 7.792.983 30.417 30.417 0 0 1 5.891 2.21c.322.16.203.65-.155.643h-.083a13.3 13.3 0 0 0-5.463 1.162 13.348 13.348 0 0 0-3.973 2.746 13.264 13.264 0 0 0-3.908 9.353 13.326 13.326 0 0 0 3.711 9.317 13.268 13.268 0 0 0 9.71 4.11 13.373 13.373 0 0 0 12.081-7.828 13.337 13.337 0 0 0 1.186-5.516v-.018c0-.363.488-.476.649-.155a30.401 30.401 0 0 1 2.186 5.898c.632 2.49.965 5.093.947 7.774Z" fill-rule="nonzero" /></svg>

After

Width:  |  Height:  |  Size: 686 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><g fill-rule="nonzero"><path d="M75.414 64.196c-3.147 1.848-6.655 3.056-10.088 4.232-4.75 1.63-2.71 9.232 2.081 7.591C73.496 73.933 87.04 70.05 87 61.485c-.036-8.263-10.97-11.646-17.692-13.666-3.08-1.281-6.839-1.027-9.616.772-7.344-7.485-8.031-16.522-2.948-25.682 2.462-4.436-4.3-8.406-6.76-3.972-6.85 12.344-5.678 25.568 4.527 35.592.415.407.858.684 1.309.867-.02.26-.039.522-.039.797v22.875c0 .333.021.656.047.976-.005.007-.014.01-.02.021L42.103 95.64a4.45 4.45 0 0 0-.715 4.754A4.403 4.403 0 0 0 45.404 103h.005l28.381-.037c2.433 0 4.403-1.989 4.4-4.432-.002-2.443-1.976-4.422-4.403-4.422h-.007l-18.594.024 5.85-6.65c1.5.67 3.175.962 4.83.887.005 1.148.014 2.407.024 3.755l7.864-.01c.377 0 .743.036 1.1.102-.054-7.021-.06-12.33-.06-12.33s-.015-.004-.017-.008c.012-.22.033-.442.033-.673v-3.624c-1.956.764-3.83 1.394-5.393 1.922-.492.163-.954.32-1.38.463a5.953 5.953 0 0 1-1.939.335c-2.67 0-4.87-2.015-5.344-4.907-.42-2.537.635-5.61 3.937-6.742 3.31-1.134 6.73-2.306 9.731-4.07.11-.063.253-.143.388-.218v-4.05c.43.206.866.398 1.288.622.96.508 1.832 1.098 2.626 1.838.219.205.326.42.381.557-.545 1.261-2.704 2.281-3.69 2.864ZM74.298 105.128l-8.298.007c.091 5.184.214 10.343.392 14.436.254 5.893 9.86 5.918 9.603 0-.173-4.01-.295-9.231-.384-14.571a7.263 7.263 0 0 1-1.313.128Z" /><path d="M57 38a8 8 0 1 0 16 0 8 8 0 0 0-16 0Z" /></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 123" fill="#fff"><g fill-rule="evenodd"><path d="m68.422 99.5 23.36-43.793 3.721.271L87.707 99.5z" /><path d="M36.41 69.008 25.516 92.8s-3.322 4.934 2.029 6.52c0 0 3.507.53 5.169-2.644l9.6-17.447s2.213-5.111 3.873-3.349l6.832 7.227s1.844 3.7 1.844 13.922c0 0-.738 2.467 4.245 2.467 0 0 3.509.178 3.509-2.289v-9.342s-.556-10.751-10.523-21.503l1.663-11.985s-.138-1.64 1.524-1.64c.756 0 2.962 3.258 6.965 6.06 1.244.872 3.522 1.867 6.83 2.983 0 0 3.554 1.404 4.78-.438 1.228-1.842.46-3.533-1.028-4.167l-6.151-2.798s-7.014-4.406-6.645-10.044c0 0 1.274-3.41-5.169-7.838-3.767-2.591-7.149-1.733-8.676-1.153l-15.689 6.875c-1.015.531-2.213 1.762-2.213 3.35v11.984c0 3.109 5.822 3.791 5.822 0v-8.285s-.102-.882 2.483-2.466c3.815-1.883 5.475-2.294 4.984-1.234 0 .176-5.169 7.578-5.169 12.16v11.28h.005ZM56.943 34.61c4.085 0 7.556-3.266 7.556-7.555 0-4.085-3.267-7.556-7.556-7.556s-7.555 3.267-7.555 7.556 3.266 7.555 7.555 7.555Z" fill-rule="nonzero" /></g></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><g fill-rule="nonzero"><path d="M74.876 38.292a7.223 7.223 0 1 0 14.447 0 7.223 7.223 0 0 0-14.447 0ZM46.12 58.036c3.843-4.637 7.022-6.955 9.536-6.955H65.92c-3.604 9.464-6.925 15.873-9.964 19.228-4.557 5.032-6.858 7.385-14.4 7.385-5.026 0-9.425-2.462-13.195-7.385l-2.603 3.61c5.704 6.725 11.42 10.546 17.149 11.466 9.515 1.526 14.678-2.551 19.387-5.845 3.77 1.846 14.843 5.34 13.899 12.674-.63 4.89-2.397 10.51-5.3 16.865h6.482c3.41-5.099 5.743-11.34 7-18.726 1.257-7.385-3.622-13.857-14.637-19.419l7.15-8.839c2.237 5.9 5.241 9.465 9.011 10.696 3.77 1.231 9.727-.212 17.869-4.329l-1.885-3.692c-6.447 1.586-10.927 1.764-13.44.533-3.77-1.846-2.815-6.637-5.683-11.491-3.168-5.36-5.385-7.455-8.822-9.456-3.437-2.002-17.02-2.932-22.695.964-3.783 2.597-6.665 6.258-8.646 10.982l3.522 1.734Z" /></g></svg>

After

Width:  |  Height:  |  Size: 915 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><defs><path id="icon_model_runSideBySide_white_a" d="M0 0h61v89H0z" /></defs><g fill-rule="evenodd"><path d="M75 38c0 5.523-4.477 10-10 10s-10-4.477-10-10 4.477-10 10-10 10 4.477 10 10" /><g transform="translate(34 26)"><mask id="icon_model_runSideBySide_white_b" fill="#fff"><use xlink:href="#icon_model_runSideBySide_white_a" /></mask><path d="M29.603 26.155s5.737 1.87 9.439-2.368c0 0 1.912-1.683 4.503-5.485L54.527 1.94S56.069-.895 59.4.289c0 0 2.345 1.558 1.357 4.176l-12.523 24.12s6.478 10.877 6.755 29.762c0 0 5.83 14.585 4.905 24.308 0 0 .278 6.358-7.033 4.862 0 0-6.385-1.87-6.94-9.256 0 0 .092-2.898 2.868-3.272l-3.053-6.638-1.944.187s-1.881 20.755-13.156 20.38c0 0-3.87.842-12.477-2.897 0 0-7.866-2.337-5.737-7.106 0 0 .555-3.272 5.83-2.43l4.442 1.776s7.094 3.179 7.942-.187c0 0 1.404-2.618 1.497-7.292l.74-9.443s-1.388-2.992-1.018-6.17c0 0 .37-10.004-2.961-18.886l-26.559-9.63S-.996 25.533.299 21.419c0 0 1.018-2.337 4.905-1.402l24.399 6.14Z" mask="url(#icon_model_runSideBySide_white_b)" /></g></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 123" fill="#fff"><defs><path id="icon_model_seating_white_a" d="M0 0h49.28v58.08H0z" /></defs><g fill-rule="evenodd"><path d="M57.08 40.48a9.24 9.24 0 1 0 0-18.48 9.24 9.24 0 0 0 0 18.48" /><g transform="translate(32 43.12)"><mask id="icon_model_seating_white_b" fill="#fff"><use xlink:href="#icon_model_seating_white_a" /></mask><path d="m49.273 32.711-.128-1.932c-.633-9.593-4.176-18.8-10.303-26.219-1.793-2.171-3.3-3.596-3.99-4.212A1.366 1.366 0 0 0 33.939 0h-17.93c-.35 0-.696.089-.995.27C2.632 7.791.013 31.436.013 31.436c-.255 4.782 3.323 4.527 3.323 4.527 3.066.064 3.258-4.527 3.258-4.527 2.939-16.516 7.028-19.449 7.028-19.449v12.371h2.81c1.517.99-.382 1.594-.382 1.594h-5.878c-1.853 0-1.725 1.466-1.725 1.466l-.192 6.568c-1.533.638-1.278 2.551-1.278 2.551l4.245 17.544a4.763 4.763 0 0 0 6.018 3.43 4.79 4.79 0 0 0 3.304-5.541l-3.216-15.752h15.78L30.362 52.52a4.768 4.768 0 0 0 3.357 5.364 4.724 4.724 0 0 0 5.982-3.647l3.273-17.314a2.18 2.18 0 0 0-1.286-2.41l-.784-.336v-6.822c-.127-1.276-1.278-1.212-1.278-1.212h-5.75c-.766-.255-.798-.669-.64-1.02.185-.403.61-.641 1.053-.653l1.76-.048V11.86c4.472 4.4 6.964 19.958 6.964 19.958-.064 4.209 3.002 4.081 3.002 4.081 3.578.064 3.259-3.188 3.259-3.188" mask="url(#icon_model_seating_white_b)" /></g><path d="M73.36 97.68h5.28V80.96h-1.824zM51.36 80.96l3.008 16.72h6.351l2.961-16.72zM35.52 80.96v16.72h5.28l-3.75-16.72z" /></g></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><g fill-rule="nonzero"><path d="M51 31a9 9 0 1 0 18 0 9 9 0 0 0-18 0ZM59.428 76.994l9.272-.507c1.933-.106 3.407-1.666 3.294-3.485l-1.722-27.895c-.112-1.818-1.77-3.208-3.7-3.101l-9.272.507c-1.933.106-3.407 1.667-3.294 3.485l1.722 27.895c.112 1.818 1.768 3.207 3.7 3.101Z" /><path d="M48.246 68.235c-.287 0-.58-.032-.872-.1l-17.435-4.058c-2.048-.475-3.319-2.505-2.837-4.528.479-2.026 2.523-3.287 4.58-2.806l14.553 3.385 8.01-15.222c.974-1.846 3.27-2.555 5.14-1.604a3.753 3.753 0 0 1 1.622 5.082l-9.38 17.822a3.82 3.82 0 0 1-3.38 2.03Zm35.804-8.63c-.65 0-1.3-.165-1.885-.494l-16.34-9.205a3.744 3.744 0 0 1-1.422-5.14c1.041-1.81 3.37-2.437 5.197-1.405l14.21 8.003 12.156-8.657c1.707-1.209 4.087-.832 5.317.859a3.741 3.741 0 0 1-.868 5.258L86.277 58.893a3.828 3.828 0 0 1-2.227.711ZM59.628 118a3.813 3.813 0 0 1-3.574-2.46l-7.912-21.158a3.738 3.738 0 0 1 .02-2.663l7.13-18.26c.762-1.94 2.977-2.894 4.93-2.156 1.962.751 2.939 2.934 2.18 4.875L55.79 93.104l7.414 19.82c.729 1.95-.278 4.12-2.253 4.842-.423.155-.87.234-1.322.234Zm22.57-5.895a3.82 3.82 0 0 1-3.091-1.562l-12.7-17.387a3.735 3.735 0 0 1-.711-1.943l-1.27-18.067c-.146-2.075 1.44-3.875 3.537-4.018a3.79 3.79 0 0 1 4.063 3.498l1.196 16.99 12.062 16.517a3.737 3.737 0 0 1-.859 5.259 3.804 3.804 0 0 1-2.227.713Z" /></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 123" fill="#fff"><g fill-rule="evenodd"><path d="M64.656 57.565h22.415c1.921 0 3.522-1.6 3.522-3.522 0-2.242-1.6-3.843-3.522-3.843H65.617a7.581 7.581 0 0 0-5.764-5.763l-.96-.32c-4.163-.961-8.646 1.6-9.607 5.763l-5.443 10.247-6.404 11.527c-2.882 3.523-2.562 8.966 1.28 11.848.32.32.961.64 1.602.96 1.28 1.281 3.202 1.602 4.803 1.281l16.33-3.842-7.364 10.247c-1.281 1.921-.961 4.803.96 6.083 1.921 1.281 4.803.961 6.084-.96l14.41-19.853c.32-.64.64-1.601.64-2.562v-2.561c-.64-3.202-3.843-4.803-7.045-4.163l-9.286 2.241m.64-27.537c4.484.64 8.967-2.242 9.927-6.725.96-4.483-1.921-8.965-6.724-9.926-4.803-.96-8.966 1.921-9.927 6.404-.32 1.601 0 3.202.32 4.483.961 2.882 3.523 5.123 6.405 5.764M33.42 49.418l3.281 1.75 6.276-11.758a1.55 1.55 0 0 0-.638-2.096l-.548-.292a1.55 1.55 0 0 0-2.096.637L33.42 49.418Z" /><path d="m27.032 48.685.76 12.2a1.55 1.55 0 0 0 2.311 1.252l10.712-6.077a1.55 1.55 0 0 0-.035-2.714l-11.473-6.124a1.549 1.549 0 0 0-2.277 1.462l.002.001Z" /></g></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 123" fill="#fff"><g fill-rule="evenodd"><path d="M64.423 57.565h22.415c1.921 0 3.522-1.6 3.522-3.522 0-2.242-1.6-3.843-3.522-3.843H65.384a7.581 7.581 0 0 0-5.764-5.763l-.96-.32c-4.163-.961-8.646 1.6-9.607 5.763L43.61 60.127l-6.404 11.527c-2.882 3.523-2.562 8.966 1.28 11.848.32.32.961.64 1.602.96 1.28 1.281 3.202 1.602 4.803 1.281l16.33-3.842-7.364 10.247c-1.281 1.921-.961 4.803.96 6.083 1.922 1.281 4.803.961 6.084-.96l14.41-19.853c.32-.64.64-1.601.64-2.562v-2.561c-.64-3.202-3.842-4.803-7.045-4.163l-9.286 2.241m.64-27.537c4.484.64 8.967-2.242 9.927-6.725.96-4.483-1.921-8.965-6.724-9.926-4.803-.96-8.966 1.921-9.926 6.404-.32 1.601 0 3.202.32 4.483.96 2.882 3.522 5.123 6.404 5.764M36.623 49.172l-3.474-1.854-6.645 12.45a1.64 1.64 0 0 0 .675 2.22l.58.309a1.64 1.64 0 0 0 2.22-.675l6.644-12.45Z" /><path d="m43.387 49.947-.805-12.918a1.64 1.64 0 0 0-2.447-1.325l-11.342 6.434a1.64 1.64 0 0 0 .037 2.875l12.148 6.483a1.64 1.64 0 0 0 2.411-1.548h-.002Z" /></g></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><defs><path id="icon_model_stand_white_a" d="M0 0h48v75.063H0z" /><path id="icon_model_stand_white_c" d="M0 0h16.247v15.748H0z" /></defs><g fill-rule="evenodd"><g transform="translate(41 40.937)"><mask id="icon_model_stand_white_b" fill="#fff"><use xlink:href="#icon_model_stand_white_a" /></mask><path d="M13.12 14.644c-1.29 4.74-2.574 9.482-3.872 14.22-.656 2.395-1.236 4.817-2.033 7.166-.745 2.194-2.517 3.127-4.48 2.558C.454 37.926-.41 36.249.182 34.143c2.334-8.305 3.985-16.805 6.98-24.916C9.086 4.012 13.215 1.005 18.765.413c5.01-.534 10.1-.917 14.932 1.287 3.737 1.705 6.368 4.426 7.497 8.277 2.313 7.894 4.467 15.834 6.589 23.78.7 2.62-.321 4.298-2.587 4.857-2.301.567-3.929-.518-4.71-3.338-1.796-6.486-3.523-12.991-5.271-19.49-.105-.39-.072-.844-.875-.987-.04.772-.114 1.531-.114 2.29-.006 18.26-.004 36.519-.006 54.778 0 3.15-.008 3.177-3.295 3.153-6.248-.046-5.459.768-5.48-5.505-.032-9.005-.008-18.01-.01-27.014 0-.667.054-1.344-.045-1.997-.104-.688-.514-1.336-1.268-1.342-.823-.007-1.46.519-1.53 1.372-.09 1.078-.055 2.165-.055 3.249-.003 9.504-.047 19.01.034 28.514.017 2.038-.606 2.82-2.755 2.764-7.058-.188-6.037.881-6.054-5.976-.042-17.342-.013-34.684-.013-52.027v-2.34l-.63-.074" mask="url(#icon_model_stand_white_b)" /></g><g transform="translate(56.874 24)"><mask id="icon_model_stand_white_d" fill="#fff"><use xlink:href="#icon_model_stand_white_c" /></mask><path d="M16.247 7.89c-.012 4.272-3.702 7.848-8.107 7.858-4.382.01-8.113-3.582-8.14-7.837C-.028 3.483 3.65-.04 8.258 0c4.473.037 8.002 3.522 7.99 7.89" mask="url(#icon_model_stand_white_d)" /></g></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 123" fill="#fff"><defs><path id="icon_model_standActive_white_a" d="M0 0h42.24v66.056H0z" /><path id="icon_model_standActive_white_c" d="M0 0h14.298v13.858H0z" /></defs><g fill-rule="evenodd"><g transform="translate(35.88 33.024)"><mask id="icon_model_standActive_white_b" fill="#fff"><use xlink:href="#icon_model_standActive_white_a" /></mask><path d="M11.545 12.887C10.411 17.058 9.28 21.231 8.138 25.4c-.577 2.108-1.087 4.239-1.79 6.306-.654 1.93-2.213 2.752-3.94 2.251-2.01-.582-2.77-2.058-2.249-3.911C2.213 22.737 3.666 15.257 6.301 8.12 7.995 3.53 11.63.884 16.513.363c4.409-.47 8.888-.807 13.14 1.133 3.29 1.5 5.604 3.895 6.597 7.284 2.036 6.946 3.932 13.934 5.799 20.927.615 2.305-.283 3.782-2.277 4.273-2.025.5-3.457-.455-4.144-2.937-1.581-5.708-3.1-11.432-4.64-17.151-.092-.344-.062-.743-.77-.87-.034.68-.099 1.348-.1 2.017-.005 16.068-.002 32.136-.005 48.204 0 2.772-.006 2.795-2.899 2.775-5.498-.04-4.804.675-4.823-4.845-.028-7.924-.006-15.848-.008-23.772 0-.587.047-1.183-.04-1.758-.09-.605-.452-1.175-1.116-1.18-.724-.007-1.284.456-1.346 1.207-.08.948-.048 1.905-.049 2.859-.002 8.364-.04 16.729.03 25.092.015 1.794-.533 2.482-2.424 2.432-6.21-.165-5.312.776-5.327-5.258-.037-15.261-.012-30.522-.012-45.784v-2.059l-.554-.065" mask="url(#icon_model_standActive_white_b)" /></g><g transform="translate(49.85 18.12)"><mask id="icon_model_standActive_white_d" fill="#fff"><use xlink:href="#icon_model_standActive_white_c" /></mask><path d="M14.298 6.944c-.011 3.758-3.259 6.906-7.135 6.914-3.856.01-7.14-3.152-7.163-6.897C-.025 3.065 3.211-.034 7.267.001c3.936.033 7.042 3.1 7.03 6.943" mask="url(#icon_model_standActive_white_d)" /></g><rect x="38" y="102.425" width="38" height="5.26" rx="2.63" /></g></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 124" fill="#fff"><g fill-rule="evenodd"><path d="M62.608 36.188a8.094 8.094 0 1 0 0-16.189 8.094 8.094 0 0 0 0 16.19M71.3 67.181v-.001c-.243-4.614-.97-5.829-.728-10.683-.242-2.914.728-14.084-.728-16.026-1.458-1.7-2.914-2.185-3.399-2.185l-4.856-.242c-4.857 0-8.013 2.184-8.498 6.068v15.635c0 1.01-.758 2.163-1.534 2.701.01.678.216 1.097.102 1.762.21-1.218-.083 1.208-.09 1.468-.028.942-.004 1.887.038 2.829.052 1.175.257 2.034.357 3.206l4.653.145a1.864 1.864 0 0 1 1.924 1.75l.62 10.327v19.423c.486 2.43 2.67 3.642 5.585 3.642 2.912 0 4.127-2.185 4.127-4.37l-.243-17.968c.73-6.313 1.214-9.955 1.7-10.925.485-1.457 1.214-1.942.97-6.556" /><path d="M57.714 70.276c.336-2.695.494-5.431.537-8.182-.008 0-.015.002-.023.003l-11.02.58c-3.886 0-5.829 1.943-6.314 4.855l-.243 20.397c.073.99.485 1.942 1.7 2.914 1.457.728 2.273 1.008 3.642.97 2.551-.072 4.333-2.437 4.37-4.128V73.36c0-.485 1.457-1.7 2.186-1.7l5.152-.18c-.036-.398-.037-.8.013-1.204" /></g></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><g fill-rule="nonzero"><path d="M90 36c9.926 0 18 8.166 18 18.201v9.14a3.66 3.66 0 1 1-7.32 0V54.2c0-5.954-4.79-10.8-10.68-10.8s-10.68 4.846-10.68 10.8V67H72V54.201C72 44.166 80.075 36 90 36Z" /><path d="M84.39 67H43.61C40.511 67 38 69.582 38 72.766v27.47c0 3.184 2.512 5.764 5.61 5.764h40.78c3.098 0 5.61-2.582 5.61-5.764v-27.47C90 69.582 87.488 67 84.39 67Z" /><path d="M75.31 89.03h-5.562c-.956 2.291-3.168 3.896-5.747 3.896-2.58 0-4.793-1.605-5.75-3.896h-5.559v-5.06h5.56c.956-2.29 3.17-3.895 5.749-3.895 2.579 0 4.79 1.605 5.747 3.895h5.561v5.06Z" /></g></svg>

After

Width:  |  Height:  |  Size: 682 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 129 140" fill="#fff"><g fill-rule="nonzero"><path d="M59.749 32.81a7.223 7.223 0 1 0 14.446 0 7.223 7.223 0 0 0-14.446 0ZM41.39 77.106c1.338-8.501 2.807-14.197 4.406-17.087 5.224-9.445 10.637-13.674 12.28-13.874 3.31-.403 7.247-.483 9.222 0 2.291.56 3.862 1.736 4.714 3.528.792 7.782 1.99 12.639 3.596 14.57 4.447 5.346 11.118 7.134 13.033 7.134 0 1.874-.969 4.956-.969 4.956-2.841 0-12.56 0-17.82-8.277-.295-.465-.697 2.294-1.207 8.277 6.813 6.471 11.216 11.722 13.208 15.752 1.992 4.03 2.986 11.72 2.981 23.069h-6.69c-.467-6.2-1.312-10.985-2.536-14.354-1.223-3.37-4.821-7.776-10.794-13.221-2.282 5.299-4.836 10.297-7.662 14.994-2.826 4.698-6.46 8.891-10.9 12.581h-6.728c5.58-7.81 9.54-13.878 11.88-18.204 2.341-4.326 4.006-8.631 4.993-12.917V57.761c-1.761.51-3.426 2.328-4.992 5.452-.975 1.944-2.692 6.575-5.153 13.893h-4.863Z" /></g></svg>

After

Width:  |  Height:  |  Size: 938 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 114 123" fill="#fff"><defs><path id="icon_model_zeroTorque_white_a" d="M0 0h61.41v46.686H0z" /></defs><g fill-rule="evenodd"><path d="M73.228 84.547H40.06a3.23 3.23 0 0 0-2.883 1.58 3.228 3.228 0 0 0 2.883 4.87h33.168a3.227 3.227 0 1 0 0-6.45M56.617 42.525a5.403 5.403 0 0 0-2.82.784 5.921 5.921 0 0 0-2.09 2.09 5.403 5.403 0 0 0-.784 2.82v15.293c0 1.005.261 1.958.784 2.82a5.932 5.932 0 0 0 2.09 2.09 5.411 5.411 0 0 0 2.82.783c1.593 0 2.938-.56 4.048-1.67 1.097-1.124 1.66-2.456 1.66-4.023V48.206c-.014-1.554-.563-2.899-1.66-4.009-1.097-1.124-2.455-1.672-4.048-1.672" /><g transform="translate(26 33)"><mask id="icon_model_zeroTorque_white_b" fill="#fff"><use xlink:href="#icon_model_zeroTorque_white_a" /></mask><path d="M41.966 30.003c0 2.05-.509 3.944-1.515 5.668-1.005 1.724-2.39 3.095-4.14 4.113a11.222 11.222 0 0 1-5.694 1.515c-2.05 0-3.944-.51-5.667-1.515a11.285 11.285 0 0 1-4.114-4.113c-1.006-1.724-1.515-3.618-1.515-5.668V15.716c0-2.05.509-3.944 1.515-5.668a11.295 11.295 0 0 1 4.114-4.114c1.736-1.018 3.617-1.515 5.667-1.515 2.051 0 3.957.51 5.694 1.515a11.178 11.178 0 0 1 4.14 4.114 11.096 11.096 0 0 1 1.515 5.668v14.287ZM58.182 0H3.228A3.228 3.228 0 0 0 0 3.228v40.23a3.227 3.227 0 0 0 3.228 3.228h54.954c.856 0 1.677-.34 2.282-.946a3.224 3.224 0 0 0 .946-2.282V3.228A3.23 3.23 0 0 0 58.182 0Z" mask="url(#icon_model_zeroTorque_white_b)" /></g></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 883 KiB

Some files were not shown because too many files have changed in this diff Show More