homebridge-roborock-vacuum 2.1.0 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.1.2
4
+
5
+ Maintenance release — no functional changes.
6
+
7
+ - **Fix**: Added the standard `name` property to `config.schema.json`. The settings UI already wrote a platform name to the config; it was only missing from the schema (flagged by the Homebridge verification checks)
8
+
9
+ ## 2.1.1
10
+
11
+ Maintenance release — no functional changes to device control.
12
+
13
+ - **Improvement**: The plugin settings page now inherits the Homebridge UI theme (theme color and light/dark mode) instead of using its own hard-coded dark palette, so it matches the rest of the Homebridge UI
14
+ - **Fix**: Corrected a malformed `repository` URL in `package.json` (`git+hhttps://…`) that broke the "Repository" link on the NPM package page
15
+
16
+ ## 2.1.0
17
+
18
+ - **New Feature**: Dock mop-wash switch
19
+ - Vacuums with a washing dock get an extra stateful switch that starts/stops a dock mop wash and mirrors the device's washing state — including washes started from the Roborock app
20
+ - Available over both HomeKit (extra Switch service on the vacuum accessory) and Matter (bridged on/off accessory per washing-capable vacuum)
21
+ - **New Feature**: `language` config option (settings-page dropdown, default `en`, adds `zh-tw`) for localized device-provided switch names. Changing the language never affects accessory UUIDs or service subtypes, so no re-pairing or room re-selection is needed
22
+ - **Fix**: Dock type detection used the attribute name instead of the actual `dock_type` value
23
+
3
24
  ## 2.0.1
4
25
 
5
26
  Stability and security hardening release based on a full plugin vulnerability scan. No new features, no configuration changes — updating is strongly recommended, especially on low-power hosts (NAS, Raspberry Pi).
@@ -7,6 +7,12 @@
7
7
  "schema": {
8
8
  "type": "object",
9
9
  "properties": {
10
+ "name": {
11
+ "title": "Name",
12
+ "type": "string",
13
+ "default": "Roborock Vacuum",
14
+ "description": "Platform name shown in the Homebridge log."
15
+ },
10
16
  "email": {
11
17
  "title": "Email",
12
18
  "type": "string",
@@ -1,6 +1,6 @@
1
- <link rel="stylesheet" href="styles.css?v=1.7.6" />
1
+ <link rel="stylesheet" href="styles.css?v=2.1.1" />
2
2
 
3
- <main class="container">
3
+ <main class="rr-container">
4
4
  <header>
5
5
  <h1>Roborock Vacuum</h1>
6
6
  <p class="subtitle">
@@ -9,169 +9,200 @@
9
9
  </header>
10
10
 
11
11
  <section class="card">
12
- <h2>Account</h2>
13
- <label>
14
- Email
15
- <input id="email" type="email" placeholder="name@example.com" />
16
- </label>
17
- <label id="password-row">
18
- Password
19
- <input id="password" type="password" placeholder="Password" />
20
- </label>
21
- <p class="help">Use password login to test credentials and save a token.</p>
22
- <label>
23
- Region
24
- <select id="base-url">
25
- <option value="https://usiot.roborock.com">
26
- US (usiot.roborock.com)
27
- </option>
28
- <option value="https://euiot.roborock.com">
29
- EU (euiot.roborock.com)
30
- </option>
31
- <option value="https://cniot.roborock.com">
32
- CN (cniot.roborock.com)
33
- </option>
34
- <option value="https://api.roborock.com">
35
- Asia (api.roborock.com)
36
- </option>
37
- </select>
38
- </label>
39
- <label>
40
- Language
41
- <select id="language">
42
- <option value="en">English</option>
43
- <option value="de">Deutsch</option>
44
- <option value="es">Español</option>
45
- <option value="fr">Français</option>
46
- <option value="it">Italiano</option>
47
- <option value="nl">Nederlands</option>
48
- <option value="pl">Polski</option>
49
- <option value="pt">Português</option>
50
- <option value="ru">Русский</option>
51
- <option value="uk">Українська</option>
52
- <option value="zh-cn">简体中文</option>
53
- <option value="zh-tw">繁體中文</option>
54
- </select>
55
- </label>
56
- <p class="help">
57
- Language for device-provided switch names (e.g. the dock mop-wash switch).
58
- Changing it only renames accessories after a restart — no re-pairing and
59
- no room re-selection is required.
60
- </p>
61
- <div class="field">
62
- <div class="field-header">
63
- <span>Devices</span>
64
- <div class="field-actions">
65
- <button
66
- id="load-devices"
67
- class="secondary btn-small"
68
- type="button"
69
- title="Load devices from your account"
70
- >
71
- Load devices
72
- </button>
73
- <button
74
- id="add-skip-device"
75
- class="icon-button"
76
- type="button"
77
- title="Add skipped device"
78
- aria-label="Add skipped device"
79
- >
80
- +
81
- </button>
12
+ <div class="card-body">
13
+ <h2>Account</h2>
14
+ <label>
15
+ Email
16
+ <input
17
+ id="email"
18
+ type="email"
19
+ class="form-control"
20
+ placeholder="name@example.com"
21
+ />
22
+ </label>
23
+ <label id="password-row">
24
+ Password
25
+ <input
26
+ id="password"
27
+ type="password"
28
+ class="form-control"
29
+ placeholder="Password"
30
+ />
31
+ </label>
32
+ <p class="help">
33
+ Use password login to test credentials and save a token.
34
+ </p>
35
+ <label>
36
+ Region
37
+ <select id="base-url" class="form-select">
38
+ <option value="https://usiot.roborock.com">
39
+ US (usiot.roborock.com)
40
+ </option>
41
+ <option value="https://euiot.roborock.com">
42
+ EU (euiot.roborock.com)
43
+ </option>
44
+ <option value="https://cniot.roborock.com">
45
+ CN (cniot.roborock.com)
46
+ </option>
47
+ <option value="https://api.roborock.com">
48
+ Asia (api.roborock.com)
49
+ </option>
50
+ </select>
51
+ </label>
52
+ <label>
53
+ Language
54
+ <select id="language" class="form-select">
55
+ <option value="en">English</option>
56
+ <option value="de">Deutsch</option>
57
+ <option value="es">Español</option>
58
+ <option value="fr">Français</option>
59
+ <option value="it">Italiano</option>
60
+ <option value="nl">Nederlands</option>
61
+ <option value="pl">Polski</option>
62
+ <option value="pt">Português</option>
63
+ <option value="ru">Русский</option>
64
+ <option value="uk">Українська</option>
65
+ <option value="zh-cn">简体中文</option>
66
+ <option value="zh-tw">繁體中文</option>
67
+ </select>
68
+ </label>
69
+ <p class="help">
70
+ Language for device-provided switch names (e.g. the dock mop-wash
71
+ switch). Changing it only renames accessories after a restart — no
72
+ re-pairing and no room re-selection is required.
73
+ </p>
74
+ <div class="field">
75
+ <div class="field-header">
76
+ <span>Devices</span>
77
+ <div class="field-actions">
78
+ <button
79
+ id="load-devices"
80
+ class="btn btn-secondary btn-sm"
81
+ type="button"
82
+ title="Load devices from your account"
83
+ >
84
+ Load devices
85
+ </button>
86
+ <button
87
+ id="add-skip-device"
88
+ class="icon-button"
89
+ type="button"
90
+ title="Add skipped device"
91
+ aria-label="Add skipped device"
92
+ >
93
+ +
94
+ </button>
95
+ </div>
82
96
  </div>
97
+ <div id="discovered-devices" class="discovered-devices"></div>
98
+ <div id="skip-devices" class="skip-device-list"></div>
83
99
  </div>
84
- <div id="discovered-devices" class="discovered-devices"></div>
85
- <div id="skip-devices" class="skip-device-list"></div>
86
- </div>
87
- <p class="help">
88
- Click <strong>Load devices</strong> to choose how each vacuum is
89
- published: <strong>HomeKit (HAP)</strong> (default),
90
- <strong>Matter</strong> (requires Homebridge 2.x with Matter enabled), or
91
- <strong>Skip</strong>. Manually entered IDs below are always skipped.
92
- Changing a device's protocol re-creates it on restart, so it loses its
93
- room and automation assignments in your home app.
94
- </p>
95
- <div id="matter-section" class="hidden">
96
- <label class="checkbox">
97
- <input id="matter-child-bridge" type="checkbox" />
98
- Enable Matter on this plugin's child bridge
99
- </label>
100
100
  <p class="help">
101
- Requires a Homebridge restart to apply. After enabling, commission this
102
- bridge's Matter side from your controller app (pairing code in the
103
- Homebridge UI bridge settings) before selecting Matter for a device.
104
- Disabling keeps the commissioning info so it can be re-enabled without
105
- re-pairing.
101
+ Click <strong>Load devices</strong> to choose how each vacuum is
102
+ published: <strong>HomeKit (HAP)</strong> (default),
103
+ <strong>Matter</strong> (requires Homebridge 2.x with Matter enabled),
104
+ or <strong>Skip</strong>. Manually entered IDs below are always skipped.
105
+ Changing a device's protocol re-creates it on restart, so it loses its
106
+ room and automation assignments in your home app.
106
107
  </p>
107
- </div>
108
- <label class="checkbox">
109
- <input id="debug-mode" type="checkbox" />
110
- Enable debug logging
111
- </label>
112
- <div class="actions">
113
- <button id="login" class="secondary">Password Login</button>
114
- <button id="logout" class="secondary hidden">Logout</button>
108
+ <div id="matter-section" class="hidden">
109
+ <label class="checkbox">
110
+ <input id="matter-child-bridge" type="checkbox" />
111
+ Enable Matter on this plugin's child bridge
112
+ </label>
113
+ <p class="help">
114
+ Requires a Homebridge restart to apply. After enabling, commission
115
+ this bridge's Matter side from your controller app (pairing code in
116
+ the Homebridge UI bridge settings) before selecting Matter for a
117
+ device. Disabling keeps the commissioning info so it can be re-enabled
118
+ without re-pairing.
119
+ </p>
120
+ </div>
121
+ <label class="checkbox">
122
+ <input id="debug-mode" type="checkbox" />
123
+ Enable debug logging
124
+ </label>
125
+ <div class="actions">
126
+ <button id="login" class="btn btn-primary">Password Login</button>
127
+ <button id="logout" class="btn btn-secondary hidden">Logout</button>
128
+ </div>
115
129
  </div>
116
130
  </section>
117
131
 
118
132
  <section class="card">
119
- <h2>Current Room → MQTT</h2>
120
- <p class="help">
121
- Publish the room each vacuum is currently cleaning to a local MQTT broker
122
- (telemetry only; not exposed to HomeKit). Off by default.
123
- </p>
124
- <label class="checkbox">
125
- <input id="room-mqtt-enabled" type="checkbox" />
126
- Publish current cleaning room to MQTT
127
- </label>
128
- <label>
129
- Broker URL
130
- <input
131
- id="room-mqtt-broker"
132
- type="text"
133
- placeholder="mqtt://127.0.0.1:1883"
134
- />
135
- </label>
136
- <label>
137
- Topic Template
138
- <input
139
- id="room-mqtt-topic"
140
- type="text"
141
- placeholder="homebridge/roborock/{duid}/current_room"
142
- />
143
- </label>
144
- <p class="help">
145
- Supports <code>{duid}</code> and <code>{name}</code> tokens; if neither is
146
- present, <code>/{duid}</code> is appended automatically so multiple
147
- vacuums never collide.
148
- </p>
149
- <label>
150
- Cleaning Poll Interval (s)
151
- <input id="room-mqtt-poll" type="number" min="5" placeholder="10" />
152
- </label>
153
- <p class="help">
154
- How often status is polled while the vacuum is actively cleaning.
155
- </p>
133
+ <div class="card-body">
134
+ <h2>Current Room → MQTT</h2>
135
+ <p class="help">
136
+ Publish the room each vacuum is currently cleaning to a local MQTT
137
+ broker (telemetry only; not exposed to HomeKit). Off by default.
138
+ </p>
139
+ <label class="checkbox">
140
+ <input id="room-mqtt-enabled" type="checkbox" />
141
+ Publish current cleaning room to MQTT
142
+ </label>
143
+ <label>
144
+ Broker URL
145
+ <input
146
+ id="room-mqtt-broker"
147
+ type="text"
148
+ class="form-control"
149
+ placeholder="mqtt://127.0.0.1:1883"
150
+ />
151
+ </label>
152
+ <label>
153
+ Topic Template
154
+ <input
155
+ id="room-mqtt-topic"
156
+ type="text"
157
+ class="form-control"
158
+ placeholder="homebridge/roborock/{duid}/current_room"
159
+ />
160
+ </label>
161
+ <p class="help">
162
+ Supports <code>{duid}</code> and <code>{name}</code> tokens; if neither
163
+ is present, <code>/{duid}</code> is appended automatically so multiple
164
+ vacuums never collide.
165
+ </p>
166
+ <label>
167
+ Cleaning Poll Interval (s)
168
+ <input
169
+ id="room-mqtt-poll"
170
+ type="number"
171
+ class="form-control"
172
+ min="5"
173
+ placeholder="10"
174
+ />
175
+ </label>
176
+ <p class="help">
177
+ How often status is polled while the vacuum is actively cleaning.
178
+ </p>
179
+ </div>
156
180
  </section>
157
181
 
158
182
  <section class="card" id="two-factor-section">
159
- <h2>Two-Factor Authentication</h2>
160
- <p class="help">
161
- If your account requires 2FA, send a verification email and enter the code
162
- below.
163
- </p>
164
- <label id="code-row">
165
- Verification Code
166
- <input id="two-factor-code" type="text" placeholder="6-digit code" />
167
- </label>
168
- <div class="actions">
169
- <button id="send-2fa" class="primary">Send 2FA Email</button>
170
- <button id="verify-2fa" class="secondary">Verify Code</button>
183
+ <div class="card-body">
184
+ <h2>Two-Factor Authentication</h2>
185
+ <p class="help">
186
+ If your account requires 2FA, send a verification email and enter the
187
+ code below.
188
+ </p>
189
+ <label id="code-row">
190
+ Verification Code
191
+ <input
192
+ id="two-factor-code"
193
+ type="text"
194
+ class="form-control"
195
+ placeholder="6-digit code"
196
+ />
197
+ </label>
198
+ <div class="actions">
199
+ <button id="send-2fa" class="btn btn-primary">Send 2FA Email</button>
200
+ <button id="verify-2fa" class="btn btn-secondary">Verify Code</button>
201
+ </div>
171
202
  </div>
172
203
  </section>
173
204
  </main>
174
205
 
175
- <div id="toast-container" class="toast-container"></div>
206
+ <div id="toast-container" class="rr-toast-container"></div>
176
207
 
177
- <script src="index.js?v=1.7.6"></script>
208
+ <script src="index.js?v=2.1.1"></script>
@@ -91,7 +91,7 @@ function showToast(type, message) {
91
91
  }
92
92
 
93
93
  const toast = document.createElement("div");
94
- toast.className = `toast ${type}`;
94
+ toast.className = `rr-toast ${type}`;
95
95
  toast.textContent = message;
96
96
  elements.toastContainer.appendChild(toast);
97
97
 
@@ -354,6 +354,7 @@ function addSkipDeviceRow(value = "", shouldFocus = true) {
354
354
 
355
355
  const input = document.createElement("input");
356
356
  input.type = "text";
357
+ input.className = "form-control";
357
358
  input.placeholder = "Device ID";
358
359
  input.setAttribute("aria-label", "Skipped device ID");
359
360
  input.value = value;
@@ -607,6 +608,7 @@ function renderDiscoveredDevices(devices) {
607
608
  label.textContent = `${device.name || device.duid}${model} — ${device.duid}${shared}`;
608
609
 
609
610
  const select = document.createElement("select");
611
+ select.className = "form-select form-select-sm";
610
612
  const current = currentProtocol(device.duid);
611
613
  const options = [{ value: "hap", text: "HomeKit (HAP)" }];
612
614
  if (matterSupported || current === "matter") {
@@ -905,7 +907,29 @@ async function updatePluginConfig(patch) {
905
907
  await withTimeout(window.homebridge.savePluginConfig(), 3000, undefined);
906
908
  }
907
909
 
910
+ // The Homebridge UI injects its themed stylesheet into this iframe shortly
911
+ // after "ready". Sample the theme's accent from a .primary-text probe once it
912
+ // lands, so native checkboxes follow the user's theme color (--rr-primary in
913
+ // styles.css). Retries because the injected stylesheet arrives asynchronously.
914
+ function adoptThemeAccent(attempt = 0) {
915
+ const probe = document.createElement("span");
916
+ probe.className = "primary-text";
917
+ probe.style.display = "none";
918
+ document.body.appendChild(probe);
919
+ const color = getComputedStyle(probe).color;
920
+ probe.remove();
921
+
922
+ if (color && color !== getComputedStyle(document.body).color) {
923
+ document.documentElement.style.setProperty("--rr-primary", color);
924
+ return;
925
+ }
926
+ if (attempt < 10) {
927
+ setTimeout(() => adoptThemeAccent(attempt + 1), 300);
928
+ }
929
+ }
930
+
908
931
  function init() {
932
+ adoptThemeAccent();
909
933
  renderSkipDevices([]);
910
934
  loadConfig().catch(() => {
911
935
  showToast("error", "Failed to load current config.");
@@ -1,74 +1,81 @@
1
- @import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600&display=swap");
1
+ /*
2
+ * Layout-only stylesheet. Color and typography come from the Homebridge UI:
3
+ * config-ui-x injects its themed stylesheet (Bootstrap 5 plus the user's
4
+ * config-ui-x-<theme> body class, plus `dark-mode` when active) into this
5
+ * iframe, so anything not overridden here follows whichever theme the user
6
+ * picked. The few greys below mirror the host's neutral palette.
7
+ */
2
8
 
3
9
  :root {
4
- --bg: #0f172a;
5
- --card: #111c33;
6
- --text: #e2e8f0;
7
- --muted: #94a3b8;
8
- --primary: #38bdf8;
9
- --secondary: #1e293b;
10
- --border: #1f2a44;
11
- --shadow: 0 20px 40px rgba(15, 23, 42, 0.35);
10
+ --rr-muted: #6c757d;
11
+ --rr-border: #dee2e6;
12
+ --rr-surface: #f8f9fa;
12
13
  }
13
14
 
14
- * {
15
- box-sizing: border-box;
15
+ /* Neutral dark surfaces matching config-ui-x's dark palette (#242424 body,
16
+ #2b2b2b cards, #333 inputs). The --bs-* variables drive the injected
17
+ Bootstrap form controls, including their focus state and select caret. */
18
+ body.dark-mode {
19
+ --rr-muted: #9e9e9e;
20
+ --rr-border: #444;
21
+ --rr-surface: #333;
22
+ --bs-body-bg: #333;
23
+ --bs-body-color: #eee;
24
+ --bs-border-color: #444;
25
+ --bs-secondary-bg: #2b2b2b;
26
+ --bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23adb5bd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
16
27
  }
17
28
 
18
- body {
19
- margin: 0;
20
- font-family: "Space Grotesk", "Segoe UI", sans-serif;
21
- background: radial-gradient(circle at top, #1e3a8a, #0b1120 55%, #020617);
22
- color: var(--text);
29
+ * {
30
+ box-sizing: border-box;
23
31
  }
24
32
 
25
- .container {
33
+ /* rr- prefix: Bootstrap's own .container would impose its breakpoint widths. */
34
+ .rr-container {
26
35
  max-width: 860px;
27
36
  margin: 0 auto;
28
- padding: 32px 24px 24px;
37
+ padding: 4px 2px 12px;
29
38
  display: grid;
30
- gap: 24px;
39
+ gap: 16px;
31
40
  }
32
41
 
33
42
  header h1 {
34
- font-size: 2.6rem;
35
- margin-bottom: 8px;
43
+ font-size: 1.3rem;
44
+ font-weight: 600;
45
+ margin: 0 0 4px;
36
46
  }
37
47
 
38
48
  .subtitle {
39
49
  margin: 0;
40
- color: var(--muted);
41
- }
42
-
43
- .card {
44
- background: var(--card);
45
- border: 1px solid var(--border);
46
- border-radius: 18px;
47
- padding: 24px;
48
- box-shadow: var(--shadow);
50
+ color: var(--rr-muted);
49
51
  }
50
52
 
51
53
  .card h2 {
52
- margin-top: 0;
53
- font-size: 1.4rem;
54
+ font-size: 1.05rem;
55
+ font-weight: 600;
56
+ margin: 0 0 4px;
54
57
  }
55
58
 
56
59
  .help {
57
- color: var(--muted);
58
- margin-top: 0;
60
+ color: var(--rr-muted);
61
+ font-size: 0.875rem;
62
+ margin: 8px 0 0;
59
63
  }
60
64
 
61
65
  label {
62
66
  display: block;
63
- margin-top: 16px;
64
- font-size: 0.95rem;
65
- color: var(--muted);
67
+ margin-top: 14px;
68
+ font-size: 0.9rem;
69
+ }
70
+
71
+ label > input,
72
+ label > select {
73
+ margin-top: 6px;
66
74
  }
67
75
 
68
76
  .field {
69
- margin-top: 16px;
70
- font-size: 0.95rem;
71
- color: var(--muted);
77
+ margin-top: 14px;
78
+ font-size: 0.9rem;
72
79
  }
73
80
 
74
81
  .field-header {
@@ -78,33 +85,15 @@ label {
78
85
  gap: 12px;
79
86
  }
80
87
 
81
- select,
82
- input,
83
- textarea {
84
- width: 100%;
85
- margin-top: 8px;
86
- padding: 12px 14px;
87
- border-radius: 10px;
88
- border: 1px solid var(--border);
89
- background: #0b1328;
90
- color: var(--text);
91
- font-size: 1rem;
92
- }
93
-
94
- textarea {
95
- min-height: 110px;
96
- resize: vertical;
97
- }
98
-
99
88
  .field-actions {
100
89
  display: flex;
101
90
  align-items: center;
102
91
  gap: 8px;
103
92
  }
104
93
 
105
- .btn-small {
106
- padding: 8px 14px;
107
- font-size: 0.85rem;
94
+ /* --rr-primary is sampled from the injected theme at runtime (index.js). */
95
+ input[type="checkbox"] {
96
+ accent-color: var(--rr-primary, auto);
108
97
  }
109
98
 
110
99
  .discovered-devices {
@@ -149,7 +138,7 @@ textarea {
149
138
  gap: 6px;
150
139
  margin: 0;
151
140
  font-size: 0.85rem;
152
- color: var(--muted);
141
+ color: var(--rr-muted);
153
142
  }
154
143
 
155
144
  .scene-toggle input[type="checkbox"] {
@@ -163,11 +152,12 @@ textarea {
163
152
  align-items: center;
164
153
  margin: 6px 0 10px;
165
154
  padding: 12px;
166
- border: 1px solid var(--border);
167
- border-radius: 12px;
168
- background: var(--secondary);
155
+ border: 1px solid var(--rr-border);
156
+ border-radius: 8px;
157
+ background: var(--rr-surface);
169
158
  }
170
159
 
160
+ /* QR codes keep a white quiet zone so they stay scannable in dark mode. */
171
161
  .pairing-qr svg {
172
162
  display: block;
173
163
  width: 132px;
@@ -203,16 +193,11 @@ textarea {
203
193
  margin-top: 0;
204
194
  }
205
195
 
206
- input::placeholder,
207
- textarea::placeholder {
208
- color: #64748b;
209
- }
210
-
211
196
  .checkbox {
212
197
  display: flex;
213
198
  align-items: center;
214
199
  gap: 10px;
215
- margin-top: 18px;
200
+ margin-top: 16px;
216
201
  }
217
202
 
218
203
  .checkbox input {
@@ -220,18 +205,16 @@ textarea::placeholder {
220
205
  margin: 0;
221
206
  }
222
207
 
208
+ /* !important: the injected sheet loads after this one, so Bootstrap's
209
+ .btn { display: inline-block } would otherwise win the tie. */
223
210
  .hidden {
224
- display: none;
225
- }
226
-
227
- .readonly input {
228
- background: #111827;
229
- opacity: 0.75;
211
+ display: none !important;
230
212
  }
231
213
 
214
+ .readonly input,
232
215
  .readonly select {
233
- background: #111827;
234
- opacity: 0.75;
216
+ opacity: 0.7;
217
+ cursor: not-allowed;
235
218
  }
236
219
 
237
220
  .actions {
@@ -241,53 +224,35 @@ textarea::placeholder {
241
224
  margin-top: 20px;
242
225
  }
243
226
 
244
- button {
245
- border: none;
246
- border-radius: 999px;
247
- padding: 12px 20px;
248
- font-size: 0.95rem;
249
- cursor: pointer;
250
- transition:
251
- transform 0.15s ease,
252
- box-shadow 0.15s ease;
253
- }
254
-
255
- button:hover {
256
- transform: translateY(-1px);
257
- box-shadow: 0 12px 24px rgba(56, 189, 248, 0.18);
258
- }
259
-
227
+ /* Compact +/- buttons; the injected theme colors their hover state. */
260
228
  .icon-button {
261
- width: 40px;
262
- min-width: 40px;
263
- height: 40px;
229
+ width: 38px;
230
+ min-width: 38px;
231
+ height: 38px;
264
232
  padding: 0;
265
233
  display: inline-flex;
266
234
  align-items: center;
267
235
  justify-content: center;
268
- font-size: 1.3rem;
236
+ font-size: 1.25rem;
269
237
  line-height: 1;
270
- background: var(--secondary);
271
- color: var(--text);
272
- border: 1px solid var(--border);
238
+ border: 1px solid var(--rr-border);
239
+ border-radius: 0.375rem;
240
+ background-color: transparent;
241
+ color: inherit;
242
+ cursor: pointer;
273
243
  }
274
244
 
275
245
  .icon-button.remove {
276
- color: #fca5a5;
246
+ color: #dc3545;
277
247
  }
278
248
 
279
- .primary {
280
- background: var(--primary);
281
- color: #041826;
249
+ body.dark-mode .icon-button.remove {
250
+ color: #f28b94;
282
251
  }
283
252
 
284
- .secondary {
285
- background: var(--secondary);
286
- color: var(--text);
287
- border: 1px solid var(--border);
288
- }
289
-
290
- .toast-container {
253
+ /* Fallback toasts, shown only when window.homebridge.toast is unavailable.
254
+ rr- prefix keeps them clear of Bootstrap's own .toast component. */
255
+ .rr-toast-container {
291
256
  position: fixed;
292
257
  right: 24px;
293
258
  bottom: 24px;
@@ -297,29 +262,38 @@ button:hover {
297
262
  z-index: 9999;
298
263
  }
299
264
 
300
- .toast {
301
- background: #0f172a;
302
- border: 1px solid var(--border);
303
- border-radius: 12px;
265
+ .rr-toast {
266
+ background: #ffffff;
267
+ border: 1px solid var(--rr-border);
268
+ border-left-width: 4px;
269
+ border-radius: 8px;
304
270
  padding: 12px 16px;
305
- box-shadow: var(--shadow);
271
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
306
272
  min-width: 220px;
307
- animation: slide-in 0.2s ease-out;
273
+ animation: rr-slide-in 0.2s ease-out;
274
+ }
275
+
276
+ body.dark-mode .rr-toast {
277
+ background: #2b2b2b;
308
278
  }
309
279
 
310
- .toast.success {
311
- border-color: #22c55e;
280
+ .rr-toast.success {
281
+ border-left-color: #28a745;
312
282
  }
313
283
 
314
- .toast.error {
315
- border-color: #ef4444;
284
+ .rr-toast.error {
285
+ border-left-color: #dc3545;
316
286
  }
317
287
 
318
- .toast.warning {
319
- border-color: #f59e0b;
288
+ .rr-toast.warning {
289
+ border-left-color: #ffc107;
320
290
  }
321
291
 
322
- @keyframes slide-in {
292
+ .rr-toast.info {
293
+ border-left-color: #17a2b8;
294
+ }
295
+
296
+ @keyframes rr-slide-in {
323
297
  from {
324
298
  transform: translateY(10px);
325
299
  opacity: 0;
@@ -329,9 +303,3 @@ button:hover {
329
303
  opacity: 1;
330
304
  }
331
305
  }
332
-
333
- @media (max-width: 640px) {
334
- header h1 {
335
- font-size: 2.1rem;
336
- }
337
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homebridge-roborock-vacuum",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "description": "Roborock Vacuum Cleaner - plugin for Homebridge.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -11,7 +11,7 @@
11
11
  ],
12
12
  "repository": {
13
13
  "type": "git",
14
- "url": "git+hhttps://github.com/tasict/homebridge-roborock-vacuum"
14
+ "url": "git+https://github.com/tasict/homebridge-roborock-vacuum.git"
15
15
  },
16
16
  "bugs": {
17
17
  "url": "https://github.com/tasict/homebridge-roborock-vacuum/issues"