@mp-consulting/homebridge-daikin-cloud 1.3.7 โ†’ 1.3.9

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.
@@ -1,34 +1,32 @@
1
1
  <!DOCTYPE html>
2
- <html data-bs-theme="auto">
2
+ <html lang="en" data-bs-theme="dark">
3
3
  <head>
4
- <script>
5
- // Auto-detect dark mode preference
6
- if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
7
- document.documentElement.setAttribute('data-bs-theme', 'dark');
8
- }
9
- window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
10
- document.documentElement.setAttribute('data-bs-theme', e.matches ? 'dark' : 'light');
11
- });
12
- </script>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
13
6
  <title>Daikin Cloud</title>
14
- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
7
+ <link rel="stylesheet" href="lib/kit.css">
15
8
  <link rel="stylesheet" href="styles.css">
16
9
  </head>
17
10
  <body>
18
11
  <div class="container-fluid p-3">
19
- <!-- Header with Status -->
20
- <div class="d-flex justify-content-between align-items-center mb-3 pb-3 border-bottom">
21
- <div class="d-flex align-items-center gap-3">
22
- <h4 class="mb-0">Daikin Cloud</h4>
12
+
13
+ <!-- Header -->
14
+ <div class="mp-header mb-3">
15
+ <i class="bi bi-wind" style="color:#007DBB;"></i>
16
+ <div class="mp-header-text">
17
+ <div class="mp-header-title">Daikin Cloud</div>
18
+ <div class="mp-header-subtitle">Onecta integration for HomeKit</div>
19
+ </div>
20
+ <div class="ms-auto d-flex align-items-center gap-2">
21
+ <span class="text-muted small" id="header-info">
22
+ <span id="token-expires-label" class="d-none">Expires in: </span>
23
+ <span id="token-expires" class="fw-medium"></span>
24
+ </span>
23
25
  <span class="badge" id="status-badge">
24
26
  <span class="status-dot" id="status-indicator"></span>
25
27
  <span id="status-text">Checking...</span>
26
28
  </span>
27
29
  </div>
28
- <div class="text-muted small" id="header-info">
29
- <span id="token-expires-label" class="d-none">Token expires in: </span>
30
- <span id="token-expires" class="fw-medium"></span>
31
- </div>
32
30
  </div>
33
31
 
34
32
  <!-- Tab Navigation -->
@@ -71,7 +69,7 @@
71
69
  </div>
72
70
  <div id="devices-list" class="list-group"></div>
73
71
  <div id="devices-empty" class="text-center py-5 d-none">
74
- <div class="fs-1 mb-2 opacity-50">๐Ÿ“ฑ</div>
72
+ <i class="bi bi-phone fs-1 mb-2 opacity-50 d-block"></i>
75
73
  <p class="mb-1">No devices found</p>
76
74
  <p class="text-muted small">Make sure you're authenticated and your Daikin devices are set up in the Onecta app.</p>
77
75
  </div>
@@ -389,7 +387,7 @@
389
387
 
390
388
  <div id="auth-url-container" class="d-none mb-3">
391
389
  <label class="form-label">Authorization URL:</label>
392
- <div class="bg-light p-2 rounded border font-monospace small text-break" id="auth-url"></div>
390
+ <div class="bg-body-secondary p-2 rounded border font-monospace small text-break" id="auth-url"></div>
393
391
  <div class="form-text">If the button doesn't work, copy this URL manually.</div>
394
392
  </div>
395
393
 
@@ -419,7 +417,7 @@
419
417
  Manual entry (if automatic redirect fails)
420
418
  </button>
421
419
  </h2>
422
- <div id="manual-callback-collapse" class="accordion-collapse collapse" id="manual-callback-details">
420
+ <div id="manual-callback-collapse" class="accordion-collapse collapse">
423
421
  <div class="accordion-body">
424
422
  <div class="alert alert-warning">
425
423
  If you see an error page after login, copy the <strong>full URL</strong> from your browser's address bar and paste it below.
@@ -439,7 +437,9 @@
439
437
  <!-- Step 3: Complete -->
440
438
  <div class="wizard-content d-none" id="content-3">
441
439
  <div class="text-center py-4">
442
- <div class="bg-success text-white rounded-circle d-inline-flex align-items-center justify-content-center mb-3" style="width: 64px; height: 64px; font-size: 32px;">โœ“</div>
440
+ <div class="bg-success text-white rounded-circle d-inline-flex align-items-center justify-content-center mb-3" style="width: 64px; height: 64px; font-size: 32px;">
441
+ <i class="bi bi-check-lg"></i>
442
+ </div>
443
443
  <h5>Authentication Successful!</h5>
444
444
  <p id="success-message" class="text-muted">Your Daikin Cloud account is now connected.</p>
445
445
  </div>
@@ -507,6 +507,16 @@
507
507
  </div>
508
508
  </div>
509
509
  </div>
510
+
511
+ <div class="mp-footer">
512
+ <a href="https://github.com/mp-consulting/homebridge-daikin-cloud" target="_blank" rel="noopener">
513
+ <i class="bi bi-github"></i>GitHub
514
+ </a>
515
+ <span class="mp-footer-sep">|</span>
516
+ <a href="https://www.npmjs.com/package/@mp-consulting/homebridge-daikin-cloud" target="_blank" rel="noopener">
517
+ <i class="bi bi-box-seam"></i>npm
518
+ </a>
519
+ </div>
510
520
  </div>
511
521
 
512
522
  <div class="loading-overlay d-none" id="loading">
@@ -517,7 +527,7 @@
517
527
 
518
528
  <div id="global-error" class="toast-container position-fixed top-0 start-50 translate-middle-x p-3 d-none"></div>
519
529
 
520
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
530
+ <script src="lib/kit.js"></script>
521
531
  <script src="script.js"></script>
522
532
  </body>
523
533
  </html>
@@ -0,0 +1,253 @@
1
+ /* @mp-consulting/homebridge-ui-kit v1.0.0 */
2
+
3
+ /* โ”€โ”€ Design tokens โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
4
+ CSS custom properties used throughout the design system.
5
+ Override these in your plugin's styles.css to customize per-plugin.
6
+ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
7
+
8
+ :root {
9
+ /* Brand */
10
+ --mp-primary: #4f46e5;
11
+ --mp-primary-rgb: 79, 70, 229;
12
+ --mp-primary-hover: #4338ca;
13
+ --mp-primary-subtle: rgba(79, 70, 229, 0.12);
14
+
15
+ /* Status */
16
+ --mp-status-online: #10b981;
17
+ --mp-status-online-glow: rgba(16, 185, 129, 0.35);
18
+ --mp-status-offline: #ef4444;
19
+ --mp-status-checking: #94a3b8;
20
+
21
+ /* Surfaces */
22
+ --mp-surface: rgba(255, 255, 255, 0.03);
23
+ --mp-border: rgba(255, 255, 255, 0.08);
24
+
25
+ /* Shape */
26
+ --mp-radius: 0.5rem;
27
+ --mp-shadow-hover: 0 0.5rem 1rem rgba(0, 0, 0, 0.3);
28
+ }
29
+
30
+ /* โ”€โ”€ Bootstrap 5 overrides โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
31
+ Remap Bootstrap components to use brand tokens.
32
+ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
33
+
34
+ /* Primary button */
35
+ .btn-primary {
36
+ --bs-btn-bg: var(--mp-primary);
37
+ --bs-btn-border-color: var(--mp-primary);
38
+ --bs-btn-hover-bg: var(--mp-primary-hover);
39
+ --bs-btn-hover-border-color: var(--mp-primary-hover);
40
+ --bs-btn-active-bg: var(--mp-primary-hover);
41
+ --bs-btn-active-border-color: var(--mp-primary-hover);
42
+ --bs-btn-focus-shadow-rgb: var(--mp-primary-rgb);
43
+ }
44
+
45
+ /* Primary spinner */
46
+ .spinner-border.text-primary,
47
+ .spinner-grow.text-primary {
48
+ color: var(--mp-primary) !important;
49
+ }
50
+
51
+ /* โ”€โ”€ Components โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
52
+ Reusable UI components used across all Homebridge plugins.
53
+ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
54
+
55
+ /* โ”€โ”€ Plugin header โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
56
+
57
+ .mp-header {
58
+ display: flex;
59
+ align-items: center;
60
+ gap: 0.875rem;
61
+ margin-bottom: 0.5rem;
62
+ }
63
+
64
+ .mp-header-icon {
65
+ font-size: 2rem;
66
+ flex-shrink: 0;
67
+ line-height: 1;
68
+ }
69
+
70
+ .mp-header-title {
71
+ font-size: 1.375rem;
72
+ font-weight: 600;
73
+ margin: 0;
74
+ line-height: 1.3;
75
+ }
76
+
77
+ .mp-header-subtitle {
78
+ font-size: 0.875rem;
79
+ color: var(--bs-secondary-color);
80
+ margin: 0;
81
+ }
82
+
83
+ /* โ”€โ”€ Status indicator dot โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
84
+
85
+ .mp-status {
86
+ width: 8px;
87
+ height: 8px;
88
+ border-radius: 50%;
89
+ display: inline-block;
90
+ flex-shrink: 0;
91
+ vertical-align: middle;
92
+ }
93
+
94
+ .mp-status-online {
95
+ background-color: var(--mp-status-online);
96
+ box-shadow: 0 0 5px var(--mp-status-online-glow);
97
+ }
98
+
99
+ .mp-status-offline {
100
+ background-color: var(--mp-status-offline);
101
+ }
102
+
103
+ .mp-status-checking {
104
+ background-color: var(--mp-status-checking);
105
+ animation: mp-pulse 1.5s ease-in-out infinite;
106
+ }
107
+
108
+ @keyframes mp-pulse {
109
+ 0%, 100% { opacity: 1; }
110
+ 50% { opacity: 0.35; }
111
+ }
112
+
113
+ /* โ”€โ”€ Device card โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
114
+
115
+ .mp-device-card {
116
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
117
+ cursor: pointer;
118
+ }
119
+
120
+ .mp-device-card:hover {
121
+ transform: translateY(-1px);
122
+ box-shadow: var(--mp-shadow-hover) !important;
123
+ }
124
+
125
+ /* โ”€โ”€ Settings surface card โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
126
+
127
+ .mp-settings-card {
128
+ background: var(--mp-surface);
129
+ border: 1px solid var(--mp-border) !important;
130
+ border-radius: var(--mp-radius);
131
+ }
132
+
133
+ /* โ”€โ”€ Empty state โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
134
+
135
+ .mp-empty-state {
136
+ display: flex;
137
+ flex-direction: column;
138
+ align-items: center;
139
+ justify-content: center;
140
+ padding: 3rem 1.5rem;
141
+ text-align: center;
142
+ }
143
+
144
+ .mp-empty-state-icon {
145
+ font-size: 2.5rem;
146
+ color: var(--bs-secondary-color);
147
+ margin-bottom: 0.75rem;
148
+ line-height: 1;
149
+ }
150
+
151
+ .mp-empty-state-title {
152
+ font-size: 0.9375rem;
153
+ font-weight: 500;
154
+ color: var(--bs-secondary-color);
155
+ margin: 0 0 0.25rem;
156
+ }
157
+
158
+ .mp-empty-state-hint {
159
+ font-size: 0.8125rem;
160
+ color: var(--bs-secondary-color);
161
+ margin: 0;
162
+ opacity: 0.7;
163
+ }
164
+
165
+ /* โ”€โ”€ Loading state โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
166
+
167
+ .mp-loading {
168
+ display: flex;
169
+ flex-direction: column;
170
+ align-items: center;
171
+ justify-content: center;
172
+ padding: 2.5rem 1.5rem;
173
+ gap: 0.75rem;
174
+ color: var(--bs-secondary-color);
175
+ font-size: 0.875rem;
176
+ }
177
+
178
+ /* โ”€โ”€ Nav tabs โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
179
+
180
+ .mp-tabs {
181
+ border-bottom: 1px solid var(--mp-border);
182
+ }
183
+
184
+ .mp-tabs .nav-link {
185
+ color: var(--bs-secondary-color);
186
+ border: none;
187
+ padding: 0.75rem 1.25rem;
188
+ font-weight: 500;
189
+ }
190
+
191
+ .mp-tabs .nav-link:hover {
192
+ color: var(--bs-body-color);
193
+ border: none;
194
+ }
195
+
196
+ .mp-tabs .nav-link.active {
197
+ color: var(--mp-primary);
198
+ background: transparent;
199
+ border: none;
200
+ border-bottom: 2px solid var(--mp-primary);
201
+ }
202
+
203
+ /* โ”€โ”€ Metadata label โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
204
+
205
+ .mp-label {
206
+ font-size: 0.75rem;
207
+ text-transform: uppercase;
208
+ letter-spacing: 0.5px;
209
+ color: var(--bs-secondary-color);
210
+ }
211
+
212
+ /* โ”€โ”€ View toggling โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
213
+
214
+ .mp-view {
215
+ display: none;
216
+ }
217
+
218
+ .mp-view.active {
219
+ display: block;
220
+ }
221
+
222
+ /* โ”€โ”€ Support footer โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
223
+
224
+ .mp-footer {
225
+ display: flex;
226
+ align-items: center;
227
+ justify-content: center;
228
+ flex-wrap: wrap;
229
+ gap: 0.75rem 1rem;
230
+ padding-top: 1.5rem;
231
+ margin-top: 2rem;
232
+ border-top: 1px solid var(--mp-border);
233
+ font-size: 0.8125rem;
234
+ color: var(--bs-secondary-color);
235
+ }
236
+
237
+ .mp-footer a {
238
+ color: var(--bs-secondary-color);
239
+ text-decoration: none;
240
+ display: inline-flex;
241
+ align-items: center;
242
+ gap: 0.3rem;
243
+ transition: color 0.15s ease;
244
+ }
245
+
246
+ .mp-footer a:hover {
247
+ color: var(--mp-primary);
248
+ }
249
+
250
+ .mp-footer-sep {
251
+ color: var(--mp-border);
252
+ user-select: none;
253
+ }
@@ -0,0 +1,133 @@
1
+ /* @mp-consulting/homebridge-ui-kit v1.0.0
2
+ Brand design system for Homebridge plugins
3
+ https://github.com/mp-consulting/homebridge-ui-kit */
4
+
5
+ (function (global) {
6
+ 'use strict';
7
+
8
+ var MpKit = {
9
+
10
+ /**
11
+ * StatusBadge โ€” returns inline HTML for device status badges.
12
+ *
13
+ * MpKit.StatusBadge.online() โ†’ green dot + "Online"
14
+ * MpKit.StatusBadge.offline() โ†’ red dot + "Offline"
15
+ * MpKit.StatusBadge.checking() โ†’ animated dot + "Checking..."
16
+ * MpKit.StatusBadge.disabled() โ†’ grey badge + "Disabled"
17
+ */
18
+ StatusBadge: {
19
+ online: function (label) {
20
+ label = label || 'Online';
21
+ return '<span class="badge bg-success-subtle text-success">'
22
+ + '<span class="mp-status mp-status-online me-1"></span>'
23
+ + label + '</span>';
24
+ },
25
+ offline: function (label) {
26
+ label = label || 'Offline';
27
+ return '<span class="badge bg-danger-subtle text-danger">'
28
+ + '<span class="mp-status mp-status-offline me-1"></span>'
29
+ + label + '</span>';
30
+ },
31
+ checking: function (label) {
32
+ label = label || 'Checking...';
33
+ return '<span class="badge bg-secondary-subtle text-secondary">'
34
+ + '<span class="mp-status mp-status-checking me-1"></span>'
35
+ + label + '</span>';
36
+ },
37
+ disabled: function (label) {
38
+ label = label || 'Disabled';
39
+ return '<span class="badge bg-secondary">' + label + '</span>';
40
+ },
41
+ },
42
+
43
+ /**
44
+ * EmptyState โ€” returns HTML for an empty list placeholder.
45
+ *
46
+ * MpKit.EmptyState.render({
47
+ * iconClass: 'bi bi-lightbulb',
48
+ * title: 'No devices found',
49
+ * hint: 'Click Discover to scan your network',
50
+ * })
51
+ */
52
+ EmptyState: {
53
+ render: function (opts) {
54
+ opts = opts || {};
55
+ var icon = opts.iconClass || 'bi bi-inbox';
56
+ var title = opts.title || 'No items';
57
+ var hint = opts.hint || '';
58
+ return '<div class="mp-empty-state">'
59
+ + '<i class="' + icon + ' mp-empty-state-icon"></i>'
60
+ + '<p class="mp-empty-state-title">' + title + '</p>'
61
+ + (hint ? '<p class="mp-empty-state-hint">' + hint + '</p>' : '')
62
+ + '</div>';
63
+ },
64
+ },
65
+
66
+ /**
67
+ * Loading โ€” returns HTML for a centred loading spinner with message.
68
+ *
69
+ * MpKit.Loading.render('Loading device information...')
70
+ */
71
+ Loading: {
72
+ render: function (message) {
73
+ message = message || 'Loading...';
74
+ return '<div class="mp-loading">'
75
+ + '<div class="spinner-border spinner-border-sm text-secondary" role="status" aria-hidden="true"></div>'
76
+ + '<span>' + message + '</span>'
77
+ + '</div>';
78
+ },
79
+ },
80
+
81
+ /**
82
+ * View โ€” controls which .mp-view element is visible.
83
+ *
84
+ * MpKit.View.show('listView') โ†’ shows #listView, hides all others
85
+ * MpKit.View.show('settingsView') โ†’ shows #settingsView, hides all others
86
+ */
87
+ View: {
88
+ show: function (id) {
89
+ document.querySelectorAll('.mp-view').forEach(function (v) {
90
+ v.classList.remove('active');
91
+ });
92
+ var el = document.getElementById(id);
93
+ if (el) { el.classList.add('active'); }
94
+ },
95
+ },
96
+
97
+ /**
98
+ * Footer โ€” renders support links into a .mp-footer element.
99
+ *
100
+ * MpKit.Footer.render({
101
+ * github: 'https://github.com/mp-consulting/homebridge-...',
102
+ * npm: 'https://www.npmjs.com/package/@mp-consulting/...',
103
+ * changelog: 'https://github.com/.../blob/main/CHANGELOG.md',
104
+ * })
105
+ */
106
+ Footer: {
107
+ render: function (opts) {
108
+ opts = opts || {};
109
+ var target = opts.target || '.mp-footer';
110
+ var el = typeof target === 'string' ? document.querySelector(target) : target;
111
+ if (!el) { return; }
112
+ var links = [];
113
+ if (opts.github) {
114
+ links.push('<a href="' + opts.github + '" target="_blank" rel="noopener">'
115
+ + '<i class="bi bi-github"></i>GitHub</a>');
116
+ }
117
+ if (opts.npm) {
118
+ links.push('<a href="' + opts.npm + '" target="_blank" rel="noopener">'
119
+ + '<i class="bi bi-box-seam"></i>npm</a>');
120
+ }
121
+ if (opts.changelog) {
122
+ links.push('<a href="' + opts.changelog + '" target="_blank" rel="noopener">'
123
+ + '<i class="bi bi-clock-history"></i>Changelog</a>');
124
+ }
125
+ el.innerHTML = links.join('<span class="mp-footer-sep">|</span>');
126
+ },
127
+ },
128
+
129
+ };
130
+
131
+ global.MpKit = MpKit;
132
+
133
+ })(window);
@@ -825,7 +825,7 @@ const Devices = {
825
825
  handleError(result) {
826
826
  if (result.message?.includes('Not authenticated')) {
827
827
  El.devicesEmpty.innerHTML = `
828
- <div class="fs-1 mb-2 opacity-50">๐Ÿ”</div>
828
+ <i class="bi bi-lock fs-1 mb-2 opacity-50 d-block text-center"></i>
829
829
  <p class="mb-1">Please authenticate first</p>
830
830
  <p class="text-muted small">Go to Authentication tab to connect.</p>`;
831
831
  DOM.show(El.devicesEmpty);
@@ -840,25 +840,25 @@ const Devices = {
840
840
  const powerOn = device.powerState === 'on';
841
841
  const mode = device.operationMode ? Utils.capitalize(device.operationMode) : '-';
842
842
  const features = device.features?.length
843
- ? `<div class="mt-2">${device.features.map(f => `<span class="badge bg-secondary me-1">${f}</span>`).join('')}</div>`
843
+ ? device.features.map(f => `<span class="badge bg-secondary bg-opacity-50 me-1">${f}</span>`).join('')
844
844
  : '';
845
845
 
846
+ const meta = [
847
+ device.roomTemp ? `<span class="device-meta-item">Room: <span class="text-body-secondary">${Utils.escapeHtml(device.roomTemp)}</span></span>` : '',
848
+ device.outdoorTemp ? `<span class="device-meta-item">Outdoor: <span class="text-body-secondary">${Utils.escapeHtml(device.outdoorTemp)}</span></span>` : '',
849
+ `<span class="device-meta-item">Mode: <span class="text-body-secondary">${Utils.escapeHtml(mode)}</span></span>`,
850
+ `<span class="device-meta-item">Model: <span class="text-body-secondary">${Utils.escapeHtml(device.model)}</span></span>`,
851
+ ].filter(Boolean).join('<span class="device-meta-sep">ยท</span>');
852
+
846
853
  return `
847
854
  <div class="list-group-item">
848
- <div class="d-flex justify-content-between align-items-center mb-2">
849
- <div class="fw-semibold">${Utils.escapeHtml(device.name)}</div>
850
- <div class="d-flex gap-1">
851
- <span class="device-power ${powerOn ? 'power-on' : 'power-off'}">${powerOn ? 'ON' : 'OFF'}</span>
852
- <span class="device-status ${online ? 'online' : 'offline'}">${online ? 'Online' : 'Offline'}</span>
853
- </div>
854
- </div>
855
- <div class="d-flex flex-wrap gap-3 small text-muted">
856
- ${device.roomTemp ? `<span><strong>Room:</strong> ${Utils.escapeHtml(device.roomTemp)}</span>` : ''}
857
- ${device.outdoorTemp ? `<span><strong>Outdoor:</strong> ${Utils.escapeHtml(device.outdoorTemp)}</span>` : ''}
858
- <span><strong>Mode:</strong> ${Utils.escapeHtml(mode)}</span>
859
- <span><strong>Model:</strong> ${Utils.escapeHtml(device.model)}</span>
855
+ <div class="d-flex align-items-center">
856
+ <span class="device-power ${powerOn ? 'power-on' : 'power-off'} me-2">${powerOn ? 'ON' : 'OFF'}</span>
857
+ <span class="fw-semibold">${Utils.escapeHtml(device.name)}</span>
858
+ ${features ? `<div class="ms-2">${features}</div>` : ''}
859
+ <span class="device-status ${online ? 'online' : 'offline'} ms-auto">${online ? 'Online' : 'Offline'}</span>
860
860
  </div>
861
- ${features}
861
+ <div class="device-meta mt-1">${meta}</div>
862
862
  </div>`;
863
863
  },
864
864
 
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * Daikin Cloud Homebridge UI - Custom Styles
3
- * Bootstrap 5 is injected by Homebridge UI, these are plugin-specific overrides
4
3
  */
5
4
 
6
5
  /* Status Badge */
@@ -106,6 +105,18 @@
106
105
  color: var(--bs-danger-text-emphasis);
107
106
  }
108
107
 
108
+ /* Device meta row */
109
+ .device-meta {
110
+ font-size: 0.75rem;
111
+ color: var(--bs-tertiary-color);
112
+ opacity: 0.55;
113
+ }
114
+
115
+ .device-meta-sep {
116
+ margin: 0 0.4rem;
117
+ opacity: 0.4;
118
+ }
119
+
109
120
  /* Device Toggle Labels */
110
121
  .device-toggle-label.visible {
111
122
  color: var(--bs-success);
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "displayName": "Daikin Cloud",
3
3
  "platformname": "daikincloud",
4
4
  "name": "@mp-consulting/homebridge-daikin-cloud",
5
- "version": "1.3.7",
5
+ "version": "1.3.9",
6
6
  "description": "Integrate with the Daikin Cloud to control your Daikin air conditioning via the cloud",
7
7
  "license": "(Apache-2.0 AND MIT)",
8
8
  "repository": {
@@ -25,7 +25,8 @@
25
25
  "test:coverage": "vitest run --coverage",
26
26
  "lint": "eslint . --max-warnings=0",
27
27
  "watch": "npm run build && npm link && nodemon",
28
- "build": "rimraf ./dist && tsc",
28
+ "copy:ui-kit": "mkdir -p homebridge-ui/public/lib && cp node_modules/@mp-consulting/homebridge-ui-kit/dist/kit.css homebridge-ui/public/lib/ && cp node_modules/@mp-consulting/homebridge-ui-kit/dist/kit.js homebridge-ui/public/lib/",
29
+ "build": "npm run copy:ui-kit && rimraf ./dist && tsc",
29
30
  "prepublishOnly": "npm run lint && npm run build",
30
31
  "release": "npm publish",
31
32
  "release:beta": "npm publish --tag beta"
@@ -40,13 +41,14 @@
40
41
  },
41
42
  "devDependencies": {
42
43
  "@eslint/js": "^9.39.2",
44
+ "@mp-consulting/homebridge-ui-kit": "^1.0.0",
43
45
  "@types/node": "^24.0.0",
44
46
  "@types/ws": "^8.18.1",
45
47
  "@vitest/coverage-v8": "^4.0.18",
46
48
  "eslint": "^9.39.2",
47
49
  "hap-nodejs": "^1.1.0",
48
50
  "homebridge": "^2.0.0-beta.55",
49
- "homebridge-config-ui-x": "^5.15.0",
51
+ "homebridge-config-ui-x": "^5.19.0",
50
52
  "nodemon": "^3.1.11",
51
53
  "rimraf": "^6.1.0",
52
54
  "typescript": "^5.9.3",