@7365admin1/layer-common 3.2.2-staging.120 → 3.2.2-staging.121

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,276 +1,229 @@
1
+ <!--
2
+ HID QR CODE CONFIGURATION - the reader's own settings, inside Site Settings.
3
+
4
+ It is mounted in `SiteSettings.vue`'s "HID Face Reader" expansion panel, next
5
+ to `HidServiceSettingsPanel`, so it takes the shape the handoff gives a
6
+ settings module BODY: section labels at 13px/700, explanatory lines at
7
+ 12-13px/600 in `--muted`, the 38x22 toggle, 40px fields and 38px buttons.
8
+
9
+ Two pieces of legacy chrome are gone rather than restyled:
10
+ * the `#082e43` navy title bar. The design puts NO card inside an accordion
11
+ body - the accordion head ("HID Face Reader") is the card, and a second
12
+ titled panel inside it is a header the design does not draw. Its title is
13
+ kept as the body's first section label.
14
+ * the chevron button that sat on that bar. It had no `@click` and no model:
15
+ it looked like a collapse control and did nothing.
16
+
17
+ Every field, toggle, dialog, handler and save call is otherwise unchanged.
18
+ -->
1
19
  <template>
2
20
  <section class="hid-qr-config">
3
- <v-card flat class="config-panel">
4
- <div class="panel-header">
21
+ <div class="config-body">
22
+ <div class="section-label config-body__title">HID Configuration</div>
23
+
24
+ <div class="online-mode-row">
5
25
  <div>
6
- <h2>HID Configuration</h2>
26
+ <h3>Activate Online Mode</h3>
27
+ <p>Enables real-time server communication, synchronizing live credential verification and access data across the network.</p>
7
28
  </div>
8
- <v-btn icon="mdi-chevron-up" variant="text" density="comfortable" />
29
+ <v-switch
30
+ v-model="form.onlineMode"
31
+ class="app-switch"
32
+ hide-details
33
+ :loading="savingSection === 'online-mode'"
34
+ :disabled="Boolean(savingSection) || !form.readerId"
35
+ @update:model-value="saveOnlineMode"
36
+ />
9
37
  </div>
10
38
 
11
- <div class="config-body">
12
- <div class="online-mode-row">
13
- <div>
14
- <h3>Activate Online Mode</h3>
15
- <p>Enables real-time server communication, synchronizing live credential verification and access data across the network.</p>
39
+ <v-divider />
40
+
41
+ <div class="form-section identification-section">
42
+ <div class="section-label">Enable/disable reader identification method</div>
43
+ <p class="section-description">
44
+ Toggles multi-factor access methods, allowing users to authenticate via biometric, physical card, or credential inputs.
45
+ </p>
46
+ <div class="methods-grid">
47
+ <div
48
+ v-for="method in identificationMethodOptions"
49
+ :key="method.key"
50
+ class="method-control"
51
+ :class="{ 'method-control--disabled': !form.onlineMode }"
52
+ >
53
+ <span>{{ method.label }}</span>
54
+ <v-switch
55
+ v-model="form.identificationMethods[method.key]"
56
+ class="app-switch"
57
+ hide-details
58
+ density="compact"
59
+ :loading="savingSection === `method-${method.key}`"
60
+ :disabled="Boolean(savingSection) || !form.onlineMode"
61
+ @update:model-value="saveIdentificationMethod(method.key)"
62
+ />
16
63
  </div>
17
- <v-switch
18
- v-model="form.onlineMode"
19
- class="hid-config-switch"
20
- color="success"
21
- hide-details
22
- inset
23
- :loading="savingSection === 'online-mode'"
24
- :disabled="Boolean(savingSection) || !form.readerId"
25
- @update:model-value="saveOnlineMode"
26
- />
27
64
  </div>
65
+ </div>
28
66
 
29
- <v-divider />
67
+ <v-divider />
68
+
69
+ <div class="form-section">
70
+ <div class="section-label">Printer Setup</div>
71
+ <div class="printer-grid">
72
+ <AppField v-model="form.printer.vendorId" placeholder="Vendor" />
73
+ <AppField v-model="form.printer.productId" placeholder="Product Id" />
74
+ <AppButton variant="ghost" icon="mdi-magnify" @click="scanPrinter">
75
+ Scan Printer
76
+ </AppButton>
77
+ <AppButton
78
+ variant="ghost"
79
+ icon="mdi-printer-check"
80
+ :disabled="!hasPrinterConfig || testingPrinter"
81
+ @click="testPrint"
82
+ >
83
+ Test &amp; Print
84
+ </AppButton>
85
+ <AppButton
86
+ icon="mdi-printer"
87
+ :disabled="savingSection === 'printer'"
88
+ @click="saveSection('printer')"
89
+ >
90
+ Save Settings
91
+ </AppButton>
92
+ </div>
93
+ </div>
30
94
 
31
- <div class="form-section identification-section">
32
- <div class="section-label">Enable/disable reader identification method</div>
33
- <p class="section-description">
34
- Toggles multi-factor access methods, allowing users to authenticate via biometric, physical card, or credential inputs.
35
- </p>
36
- <div class="methods-grid">
37
- <div
38
- v-for="method in identificationMethodOptions"
39
- :key="method.key"
40
- class="method-control"
41
- :class="{ 'method-control--disabled': !form.onlineMode }"
42
- >
43
- <span>{{ method.label }}</span>
44
- <v-switch
45
- v-model="form.identificationMethods[method.key]"
46
- class="hid-config-switch"
47
- color="success"
48
- hide-details
49
- inset
50
- density="compact"
51
- :loading="savingSection === `method-${method.key}`"
52
- :disabled="Boolean(savingSection) || !form.onlineMode"
53
- @update:model-value="saveIdentificationMethod(method.key)"
54
- />
55
- </div>
56
- </div>
95
+ <v-divider />
96
+
97
+ <div class="form-section">
98
+ <div class="section-label">HID QR Code Template</div>
99
+ <div class="template-grid">
100
+ <AppField v-model="form.template.header" placeholder="Header" />
101
+ <AppField v-model="form.template.subtext" placeholder="Subtext" />
102
+ <AppButton variant="ghost" icon="mdi-eye" @click="previewDialog = true">
103
+ Preview
104
+ </AppButton>
105
+ <AppButton
106
+ icon="mdi-qrcode-scan"
107
+ :disabled="savingSection === 'template'"
108
+ @click="saveSection('template')"
109
+ >
110
+ Save
111
+ </AppButton>
57
112
  </div>
113
+ </div>
58
114
 
59
- <v-divider />
115
+ <v-divider />
60
116
 
61
- <div class="form-section">
62
- <div class="section-label">Printer Setup</div>
63
- <div class="printer-grid">
64
- <v-text-field
65
- v-model="form.printer.vendorId"
66
- placeholder="Vendor"
67
- variant="outlined"
68
- density="compact"
69
- hide-details
70
- />
71
- <v-text-field
72
- v-model="form.printer.productId"
73
- placeholder="Product Id"
74
- variant="outlined"
75
- density="compact"
76
- hide-details
77
- />
78
- <v-btn
79
- class="text-none utility-button"
80
- variant="outlined"
81
- prepend-icon="mdi-magnify"
82
- height="40"
83
- @click="scanPrinter"
84
- >
85
- Scan Printer
86
- </v-btn>
87
- <v-btn
88
- class="text-none test-button"
89
- variant="outlined"
90
- prepend-icon="mdi-printer-check"
91
- height="40"
92
- :loading="testingPrinter"
93
- :disabled="!hasPrinterConfig"
94
- @click="testPrint"
95
- >
96
- Test & Print
97
- </v-btn>
98
- <v-btn
99
- class="text-none save-button"
100
- variant="flat"
101
- prepend-icon="mdi-printer"
102
- height="40"
103
- :loading="savingSection === 'printer'"
104
- @click="saveSection('printer')"
105
- >
106
- Save Settings
107
- </v-btn>
108
- </div>
117
+ <div class="form-section">
118
+ <div class="section-label">HID QR Code Validity</div>
119
+ <div class="validity-grid">
120
+ <AppField v-model="validityInput" type="number" placeholder="Validity (minutes)" />
121
+ <AppButton
122
+ icon="mdi-timer-outline"
123
+ :disabled="savingSection === 'validity'"
124
+ @click="saveSection('validity')"
125
+ >
126
+ Save
127
+ </AppButton>
109
128
  </div>
129
+ </div>
110
130
 
111
- <v-divider />
131
+ <v-divider />
112
132
 
113
- <div class="form-section">
114
- <div class="section-label">HID QR Code Template</div>
115
- <div class="template-grid">
116
- <v-text-field
117
- v-model="form.template.header"
118
- placeholder="Header"
119
- variant="outlined"
120
- density="compact"
121
- hide-details
122
- />
123
- <v-text-field
124
- v-model="form.template.subtext"
125
- placeholder="Subtext"
126
- variant="outlined"
127
- density="compact"
128
- hide-details
129
- />
130
- <v-btn
131
- class="text-none preview-button"
132
- variant="flat"
133
- prepend-icon="mdi-eye"
134
- height="40"
135
- @click="previewDialog = true"
136
- >
137
- Preview
138
- </v-btn>
139
- <v-btn
140
- class="text-none save-button"
141
- variant="flat"
142
- prepend-icon="mdi-qrcode-scan"
143
- height="40"
144
- :loading="savingSection === 'template'"
145
- @click="saveSection('template')"
146
- >
147
- Save
148
- </v-btn>
133
+ <div v-if="isPermissionManager" class="permission-section">
134
+ <div class="permission-location">
135
+ <div class="permission-copy">
136
+ <div class="section-label">Permission Location</div>
137
+ <p class="section-description">
138
+ Permissions are scoped to the location configured on the HID Face Reader.
139
+ </p>
149
140
  </div>
141
+ <AppSelect
142
+ v-model="permissionReaderId"
143
+ class="permission-location__select"
144
+ :items="permissionLocations"
145
+ item-title="label"
146
+ item-value="readerId"
147
+ placeholder="Select a Face Reader location"
148
+ :disabled="permissionLocations.length === 0"
149
+ @update:model-value="loadPermissions"
150
+ />
150
151
  </div>
151
152
 
153
+ <v-alert
154
+ v-if="permissionLocations.length === 0"
155
+ class="mb-4"
156
+ type="warning"
157
+ variant="tonal"
158
+ density="compact"
159
+ >
160
+ Add a Location to a Face Reader before assigning HID permissions.
161
+ </v-alert>
162
+
152
163
  <v-divider />
153
164
 
154
- <div class="form-section">
155
- <div class="section-label">HID QR Code Validity</div>
156
- <div class="validity-grid">
157
- <v-text-field
158
- v-model.number="form.validityMinutes"
159
- placeholder="Validity (minutes)"
160
- variant="outlined"
161
- density="compact"
162
- hide-details
163
- min="1"
164
- type="number"
165
- />
166
- <v-btn
167
- class="text-none save-button"
168
- variant="flat"
169
- prepend-icon="mdi-timer-outline"
170
- height="40"
171
- :loading="savingSection === 'validity'"
172
- @click="saveSection('validity')"
165
+ <div class="permission-row">
166
+ <div class="permission-copy">
167
+ <div class="section-label">Access Permission</div>
168
+ <p class="section-description">
169
+ Select who can use the enabled HID identification methods at this site.
170
+ </p>
171
+ </div>
172
+ <div class="permission-actions">
173
+ <AppButton
174
+ v-for="option in accessPermissionOptions"
175
+ :key="option.category"
176
+ variant="ghost"
177
+ class="permission-button"
178
+ :disabled="!form.onlineMode || !permissionReaderId"
179
+ @click="openPermissionDialog(option.category)"
173
180
  >
174
- Save
175
- </v-btn>
181
+ <span class="permission-count">{{ permissionCount(option.category) }}</span>
182
+ <span>{{ option.label }}</span>
183
+ </AppButton>
176
184
  </div>
177
185
  </div>
178
186
 
179
187
  <v-divider />
180
188
 
181
- <div v-if="isPermissionManager" class="permission-section">
182
- <div class="permission-location">
183
- <div class="permission-copy">
184
- <div class="section-label">Permission Location</div>
185
- <p class="section-description">
186
- Permissions are scoped to the location configured on the HID Face Reader.
187
- </p>
188
- </div>
189
- <v-select
190
- v-model="permissionReaderId"
191
- class="permission-location__select"
192
- :items="permissionLocations"
193
- item-title="label"
194
- item-value="readerId"
195
- placeholder="Select a Face Reader location"
196
- variant="outlined"
197
- density="compact"
198
- hide-details
199
- :disabled="permissionLocations.length === 0"
200
- @update:model-value="loadPermissions"
201
- />
189
+ <div class="permission-row permission-row--intercom">
190
+ <div class="permission-copy">
191
+ <div class="section-label">Access Intercom</div>
192
+ <p class="section-description">
193
+ Choose which authorized people and providers may use HID intercom access.
194
+ </p>
202
195
  </div>
203
-
204
- <v-alert
205
- v-if="permissionLocations.length === 0"
206
- class="mb-4"
207
- type="warning"
208
- variant="tonal"
209
- density="compact"
210
- >
211
- Add a Location to a Face Reader before assigning HID permissions.
212
- </v-alert>
213
-
214
- <v-divider />
215
-
216
- <div class="permission-row">
217
- <div class="permission-copy">
218
- <div class="section-label">Access Permission</div>
219
- <p class="section-description">
220
- Select who can use the enabled HID identification methods at this site.
221
- </p>
222
- </div>
223
- <div class="permission-actions">
224
- <v-btn
225
- v-for="option in accessPermissionOptions"
226
- :key="option.category"
227
- class="permission-button text-none"
228
- variant="outlined"
229
- :disabled="!form.onlineMode || !permissionReaderId"
230
- @click="openPermissionDialog(option.category)"
231
- >
232
- <span class="permission-count">{{ permissionCount(option.category) }}</span>
233
- <span>{{ option.label }}</span>
234
- </v-btn>
235
- </div>
236
- </div>
237
-
238
- <v-divider />
239
-
240
- <div class="permission-row permission-row--intercom">
241
- <div class="permission-copy">
242
- <div class="section-label">Access Intercom</div>
243
- <p class="section-description">
244
- Choose which authorized people and providers may use HID intercom access.
245
- </p>
246
- </div>
247
- <div class="permission-actions permission-actions--single">
248
- <v-btn
249
- class="permission-button text-none"
250
- variant="outlined"
251
- :disabled="!form.onlineMode || !permissionReaderId || permissionState.assignments.length === 0"
252
- @click="openPermissionDialog('intercom')"
253
- >
254
- <span class="permission-count">{{ permissionState.counts.intercom }}</span>
255
- <span>Intercom Permissions</span>
256
- </v-btn>
257
- </div>
196
+ <div class="permission-actions permission-actions--single">
197
+ <AppButton
198
+ variant="ghost"
199
+ class="permission-button"
200
+ :disabled="!form.onlineMode || !permissionReaderId || permissionState.assignments.length === 0"
201
+ @click="openPermissionDialog('intercom')"
202
+ >
203
+ <span class="permission-count">{{ permissionState.counts.intercom }}</span>
204
+ <span>Intercom Permissions</span>
205
+ </AppButton>
258
206
  </div>
259
207
  </div>
260
208
  </div>
261
- </v-card>
209
+ </div>
262
210
 
263
211
  <v-dialog v-model="previewDialog" max-width="420">
264
212
  <v-card class="preview-shell">
265
- <v-btn
213
+ <AppButton
214
+ variant="icon"
266
215
  class="preview-close"
267
216
  icon="mdi-close"
268
- variant="text"
269
- density="comfortable"
270
217
  @click="previewDialog = false"
271
218
  />
272
219
  <v-card-text>
273
220
  <div class="preview-title">QR code Preview</div>
221
+ <!--
222
+ THE PRINTED PASS. Deliberately NOT themed: this is a preview of a
223
+ piece of card that comes out of a printer, so it is white with black
224
+ ink on both themes, the way the physical pass is. Everything AROUND
225
+ it sits on the dialog scrim, which is dark in both themes.
226
+ -->
274
227
  <div class="pass-preview">
275
228
  <div class="pass-heading">{{ form.template.header || "Header" }}</div>
276
229
  <div class="pass-subtext">{{ form.template.subtext || "Subtext" }}</div>
@@ -302,25 +255,19 @@
302
255
  <div class="permission-dialog__title">{{ permissionDialogTitle }}</div>
303
256
  <div class="permission-dialog__subtitle">{{ permissionDialogDescription }}</div>
304
257
  </div>
305
- <v-btn
258
+ <AppButton
259
+ variant="icon"
306
260
  icon="mdi-close"
307
- variant="text"
308
- density="comfortable"
309
261
  aria-label="Close permission dialog"
310
262
  @click="closePermissionDialog"
311
263
  />
312
264
  </v-card-title>
313
265
 
314
266
  <v-card-text class="permission-dialog__body">
315
- <v-text-field
267
+ <AppField
316
268
  v-model="permissionSearch"
317
- class="permission-search"
318
- prepend-inner-icon="mdi-magnify"
269
+ search
319
270
  placeholder="Search by name or details"
320
- variant="outlined"
321
- density="compact"
322
- hide-details
323
- clearable
324
271
  />
325
272
 
326
273
  <div class="permission-list-summary" aria-live="polite">
@@ -332,7 +279,7 @@
332
279
 
333
280
  <div class="permission-list" :class="{ 'permission-list--loading': loadingPermissionCandidates }">
334
281
  <div v-if="loadingPermissionCandidates" class="permission-empty">
335
- <v-progress-circular indeterminate color="primary" size="28" />
282
+ <v-progress-circular indeterminate class="permission-spinner" size="28" />
336
283
  <span>Loading permissions...</span>
337
284
  </div>
338
285
 
@@ -352,7 +299,6 @@
352
299
  class="permission-list-item__checkbox"
353
300
  :model-value="selectedPermissionIds.has(candidate.subjectId)"
354
301
  :aria-label="`Select ${candidate.name}`"
355
- color="success"
356
302
  @update:model-value="togglePermissionCandidate(candidate.subjectId, Boolean($event))"
357
303
  />
358
304
  </label>
@@ -366,17 +312,13 @@
366
312
  </v-card-text>
367
313
 
368
314
  <v-card-actions class="permission-dialog__actions">
369
- <v-btn class="text-none" variant="text" @click="closePermissionDialog">Cancel</v-btn>
370
- <v-btn
371
- class="text-none save-button"
372
- variant="flat"
373
- min-width="140"
374
- :loading="savingPermissions"
375
- :disabled="loadingPermissionCandidates"
315
+ <AppButton variant="ghost" @click="closePermissionDialog">Cancel</AppButton>
316
+ <AppButton
317
+ :disabled="loadingPermissionCandidates || savingPermissions"
376
318
  @click="savePermissions"
377
319
  >
378
320
  Save Permissions
379
- </v-btn>
321
+ </AppButton>
380
322
  </v-card-actions>
381
323
  </v-card>
382
324
  </v-dialog>
@@ -385,6 +327,7 @@
385
327
  </section>
386
328
  </template>
387
329
 
330
+
388
331
  <script setup lang="ts">
389
332
  type HidIdentificationMethod = keyof NonNullable<THidQrCodePassConfig["identificationMethods"]>;
390
333
 
@@ -536,6 +479,19 @@ const siteLabel = computed(() => siteData.value?.code || siteData.value?.block |
536
479
  const validityText = computed(() =>
537
480
  form.validityMinutes ? `${form.validityMinutes} minutes` : "Not set",
538
481
  );
482
+
483
+ /**
484
+ * `AppField` is a native `<input>` and its model is a string, where
485
+ * `form.validityMinutes` is a `number | null` on the config type. This keeps
486
+ * the field's value exactly the number `v-model.number` used to put there -
487
+ * empty stays `null`, not `NaN` and not `""`.
488
+ */
489
+ const validityInput = computed({
490
+ get: () => (form.validityMinutes === null || form.validityMinutes === undefined ? "" : String(form.validityMinutes)),
491
+ set: (value: string) => {
492
+ form.validityMinutes = value === "" ? null : Number(value);
493
+ },
494
+ });
539
495
  const visiblePermissionCandidates = computed(() => {
540
496
  const search = permissionSearch.value.trim().toLowerCase();
541
497
  if (!search) return permissionCandidates.value;
@@ -904,38 +860,32 @@ function showMessage(text: string, color: string) {
904
860
  }
905
861
  </script>
906
862
 
863
+
907
864
  <style scoped>
865
+ /*
866
+ EVERY COLOUR IS A TOKEN NOW.
867
+
868
+ This file carried 38 distinct literal hexes - a navy `#082e43` bar, greens
869
+ `#2eaf55` / `#35b84b` / `#34b84a` / `#2da643`, greys `#233545` / `#677280` /
870
+ `#334454` / `#8b9297` / `#dce2e7` / `#e6eaee` and a dozen more. A literal is
871
+ the same colour on both themes, which is why this screen had no dark theme at
872
+ all. The LAYOUT (the grids, the breakpoints, the 42px method boxes) is the
873
+ screen's own and is kept; only the palette moved.
874
+
875
+ The one deliberate exception is `.pass-preview` - see the template comment.
876
+ */
908
877
  .hid-qr-config {
909
878
  width: 100%;
910
879
  min-width: 0;
911
880
  padding: 8px 0 28px;
912
881
  }
913
882
 
914
- .config-panel {
915
- border: 1px solid #e6eaee;
916
- border-radius: 6px;
917
- overflow: hidden;
918
- background: #ffffff;
919
- }
920
-
921
- .panel-header {
922
- display: flex;
923
- align-items: center;
924
- justify-content: space-between;
925
- min-height: 48px;
926
- padding: 0 18px;
927
- background: #082e43;
928
- color: #ffffff;
929
- }
930
-
931
- .panel-header h2 {
932
- margin: 0;
933
- font-size: 13px;
934
- font-weight: 700;
883
+ .config-body {
884
+ padding: 4px 0 0;
935
885
  }
936
886
 
937
- .config-body {
938
- padding: 30px 38px;
887
+ .config-body__title {
888
+ margin-bottom: 14px;
939
889
  }
940
890
 
941
891
  .online-mode-row {
@@ -946,10 +896,13 @@ function showMessage(text: string, color: string) {
946
896
  padding: 0 0 20px;
947
897
  }
948
898
 
899
+ /* The prototype's settings body: a 13px/700 label over a 12-13px/600 muted
900
+ line. */
949
901
  .online-mode-row h3,
950
902
  .section-label {
951
903
  margin: 0 0 6px;
952
- color: #233545;
904
+ color: var(--text);
905
+ font-family: var(--font-sans);
953
906
  font-size: 13px;
954
907
  font-weight: 700;
955
908
  }
@@ -957,8 +910,10 @@ function showMessage(text: string, color: string) {
957
910
  .online-mode-row p,
958
911
  .section-description {
959
912
  margin: 0;
960
- color: #677280;
913
+ color: var(--muted);
914
+ font-family: var(--font-sans);
961
915
  font-size: 12px;
916
+ font-weight: 600;
962
917
  line-height: 1.5;
963
918
  }
964
919
 
@@ -984,10 +939,12 @@ function showMessage(text: string, color: string) {
984
939
  min-width: 0;
985
940
  min-height: 42px;
986
941
  padding: 0 10px 0 14px;
987
- border: 1px solid #dce2e7;
988
- border-radius: 6px;
989
- color: #334454;
942
+ border: 1px solid var(--border);
943
+ border-radius: var(--r-inner);
944
+ color: var(--text2);
945
+ font-family: var(--font-sans);
990
946
  font-size: 12px;
947
+ font-weight: 700;
991
948
  }
992
949
 
993
950
  .method-control span {
@@ -997,8 +954,8 @@ function showMessage(text: string, color: string) {
997
954
  }
998
955
 
999
956
  .method-control--disabled {
1000
- background-color: #f5f6f7;
1001
- color: #8b9297;
957
+ background-color: var(--hover);
958
+ color: var(--muted);
1002
959
  }
1003
960
 
1004
961
  .method-control :deep(.v-switch) {
@@ -1009,33 +966,15 @@ function showMessage(text: string, color: string) {
1009
966
  min-height: 32px;
1010
967
  }
1011
968
 
1012
- .hid-config-switch :deep(.v-switch__track) {
1013
- background-color: #8b9297;
1014
- opacity: 1;
1015
- }
1016
-
1017
- .hid-config-switch :deep(.v-selection-control--dirty .v-switch__track) {
1018
- background-color: #2eaf55 !important;
1019
- color: #2eaf55 !important;
1020
- opacity: 1;
1021
- }
1022
-
1023
- .hid-config-switch :deep(.v-switch__thumb) {
1024
- background-color: #fff !important;
1025
- color: #fff !important;
1026
- }
1027
-
1028
- .hid-config-switch.v-input--disabled :deep(.v-switch__track) {
1029
- background-color: #aeb4b8 !important;
1030
- color: #aeb4b8 !important;
1031
- opacity: 1;
1032
- }
969
+ /* The three `.hid-config-switch` rules are gone: `.app-switch` in
970
+ primitives.css is the handoff's toggle, and it is shared with the other two
971
+ HID panels that have one. */
1033
972
 
1034
973
  .printer-grid {
1035
974
  display: grid;
1036
975
  grid-template-columns: minmax(140px, 1fr) minmax(140px, 1fr) minmax(140px, 1fr) minmax(140px, 1fr) minmax(140px, 1fr);
1037
976
  gap: 14px;
1038
- align-items: center;
977
+ align-items: end;
1039
978
  }
1040
979
 
1041
980
  .template-grid {
@@ -1044,16 +983,11 @@ function showMessage(text: string, color: string) {
1044
983
  gap: 14px;
1045
984
  }
1046
985
 
1047
- .template-grid .preview-button,
1048
- .template-grid .save-button {
1049
- width: 100%;
1050
- }
1051
-
1052
986
  .validity-grid {
1053
987
  display: grid;
1054
988
  grid-template-columns: minmax(180px, 260px) minmax(140px, 260px);
1055
989
  gap: 14px;
1056
- align-items: center;
990
+ align-items: end;
1057
991
  }
1058
992
 
1059
993
  .permission-section {
@@ -1099,12 +1033,11 @@ function showMessage(text: string, color: string) {
1099
1033
  justify-content: end;
1100
1034
  }
1101
1035
 
1036
+ /* A ghost button carrying a count badge. `AppButton --ghost` supplies the
1037
+ height, radius, border and type; only the alignment is local. */
1102
1038
  .permission-button {
1103
1039
  justify-content: flex-start;
1104
1040
  min-height: 42px;
1105
- padding-inline: 12px;
1106
- border-color: #dce2e7;
1107
- color: #334454;
1108
1041
  }
1109
1042
 
1110
1043
  .permission-count {
@@ -1114,20 +1047,21 @@ function showMessage(text: string, color: string) {
1114
1047
  height: 20px;
1115
1048
  margin-right: 8px;
1116
1049
  border-radius: 50%;
1117
- background: #dce2e7;
1118
- color: #526170;
1050
+ background: var(--accent-soft);
1051
+ color: var(--accent-text);
1119
1052
  font-size: 11px;
1120
1053
  font-weight: 700;
1121
1054
  }
1122
1055
 
1123
1056
  .permission-button:not(:disabled):hover {
1124
- border-color: #1473c9;
1125
- color: #0a5da7;
1057
+ border-color: var(--accent);
1058
+ color: var(--accent-text);
1126
1059
  }
1127
1060
 
1128
1061
  .permission-dialog {
1129
1062
  overflow: hidden;
1130
- border-radius: 6px;
1063
+ border-radius: var(--r-modal);
1064
+ background: var(--card);
1131
1065
  }
1132
1066
 
1133
1067
  .permission-dialog__header {
@@ -1136,19 +1070,21 @@ function showMessage(text: string, color: string) {
1136
1070
  justify-content: space-between;
1137
1071
  gap: 16px;
1138
1072
  padding: 16px 18px 14px;
1139
- border-bottom: 1px solid #e5e9ed;
1073
+ border-bottom: 1px solid var(--border);
1140
1074
  }
1141
1075
 
1142
1076
  .permission-dialog__title {
1143
- color: #172534;
1077
+ color: var(--text);
1078
+ font-family: var(--font-sans);
1144
1079
  font-size: 18px;
1145
- font-weight: 700;
1080
+ font-weight: 800;
1146
1081
  }
1147
1082
 
1148
1083
  .permission-dialog__subtitle {
1149
1084
  margin-top: 4px;
1150
- color: #687583;
1085
+ color: var(--muted);
1151
1086
  font-size: 12px;
1087
+ font-weight: 600;
1152
1088
  white-space: normal;
1153
1089
  }
1154
1090
 
@@ -1158,21 +1094,18 @@ function showMessage(text: string, color: string) {
1158
1094
  padding: 14px 18px 16px;
1159
1095
  }
1160
1096
 
1161
- .permission-search :deep(.v-field) {
1162
- min-height: 44px;
1163
- }
1164
-
1165
1097
  .permission-list-summary {
1166
1098
  display: flex;
1167
1099
  align-items: center;
1168
1100
  justify-content: space-between;
1169
1101
  min-height: 22px;
1170
- color: #778391;
1102
+ color: var(--muted);
1171
1103
  font-size: 11px;
1104
+ font-weight: 600;
1172
1105
  }
1173
1106
 
1174
1107
  .permission-list-summary__selected {
1175
- color: #286c3c;
1108
+ color: var(--ok);
1176
1109
  font-weight: 700;
1177
1110
  }
1178
1111
 
@@ -1180,9 +1113,9 @@ function showMessage(text: string, color: string) {
1180
1113
  min-height: 180px;
1181
1114
  max-height: min(360px, 48vh);
1182
1115
  overflow-y: auto;
1183
- border: 1px solid #dfe5ea;
1184
- border-radius: 6px;
1185
- background: #ffffff;
1116
+ border: 1px solid var(--border);
1117
+ border-radius: var(--r-inner);
1118
+ background: var(--card);
1186
1119
  }
1187
1120
 
1188
1121
  .permission-list--loading {
@@ -1197,9 +1130,9 @@ function showMessage(text: string, color: string) {
1197
1130
  align-items: center;
1198
1131
  min-height: 56px;
1199
1132
  padding: 7px 12px;
1200
- border-bottom: 1px solid #edf0f3;
1133
+ border-bottom: 1px solid var(--border);
1201
1134
  cursor: pointer;
1202
- transition: background-color 120ms ease;
1135
+ transition: background-color var(--motion) ease;
1203
1136
  }
1204
1137
 
1205
1138
  .permission-list-item:last-child {
@@ -1207,15 +1140,12 @@ function showMessage(text: string, color: string) {
1207
1140
  }
1208
1141
 
1209
1142
  .permission-list-item:hover {
1210
- background: #f7fafc;
1211
- }
1212
-
1213
- .permission-list-item--selected {
1214
- background: #f1f8f3;
1143
+ background: var(--hover);
1215
1144
  }
1216
1145
 
1146
+ .permission-list-item--selected,
1217
1147
  .permission-list-item--selected:hover {
1218
- background: #eaf5ed;
1148
+ background: var(--accent-soft);
1219
1149
  }
1220
1150
 
1221
1151
  .permission-list-item__content {
@@ -1232,23 +1162,25 @@ function showMessage(text: string, color: string) {
1232
1162
  }
1233
1163
 
1234
1164
  .permission-list-item__content strong {
1235
- color: #243443;
1165
+ color: var(--text);
1236
1166
  font-size: 13px;
1167
+ font-weight: 700;
1237
1168
  line-height: 1.35;
1238
1169
  }
1239
1170
 
1240
1171
  .permission-list-item__content small,
1241
1172
  .permission-list-item__group {
1242
- color: #778391;
1173
+ color: var(--muted);
1243
1174
  font-size: 11px;
1175
+ font-weight: 600;
1244
1176
  }
1245
1177
 
1246
1178
  .permission-list-item__group {
1247
1179
  max-width: 170px;
1248
1180
  overflow: hidden;
1249
1181
  padding: 4px 9px;
1250
- border-radius: 4px;
1251
- background: #eef2f5;
1182
+ border-radius: var(--r-tag);
1183
+ background: var(--hover);
1252
1184
  text-overflow: ellipsis;
1253
1185
  white-space: nowrap;
1254
1186
  }
@@ -1269,37 +1201,31 @@ function showMessage(text: string, color: string) {
1269
1201
  gap: 10px;
1270
1202
  min-height: 180px;
1271
1203
  padding: 24px;
1272
- color: #75818c;
1204
+ color: var(--muted);
1273
1205
  font-size: 13px;
1274
1206
  text-align: center;
1275
1207
  }
1276
1208
 
1209
+ .permission-spinner {
1210
+ color: var(--accent);
1211
+ }
1212
+
1277
1213
  .permission-dialog__actions {
1278
1214
  justify-content: flex-end;
1279
1215
  gap: 8px;
1280
1216
  padding: 12px 18px;
1281
- border-top: 1px solid #e5e9ed;
1282
- }
1283
-
1284
- .save-button {
1285
- background: #082e43;
1286
- color: #ffffff;
1217
+ border-top: 1px solid var(--border);
1287
1218
  }
1288
1219
 
1289
- .preview-button {
1290
- background: #35b84b;
1291
- color: #ffffff;
1292
- }
1293
-
1294
- .test-button {
1295
- border-color: #34b84a;
1296
- color: #2da643;
1297
- }
1298
-
1299
- .utility-button {
1300
- color: #1f3448;
1301
- }
1220
+ /*
1221
+ THE PRINTED PASS - the one thing here that is NOT themed.
1302
1222
 
1223
+ It previews a physical card that comes out of a badge printer, so it stays
1224
+ white with dark ink on both themes; theming it would show the operator a
1225
+ preview of something the printer cannot produce. Everything around it sits on
1226
+ the dialog scrim, which is dark on both themes - which is why the title above
1227
+ it is white.
1228
+ */
1303
1229
  .preview-shell {
1304
1230
  position: relative;
1305
1231
  overflow: visible;
@@ -1312,13 +1238,15 @@ function showMessage(text: string, color: string) {
1312
1238
  top: 38px;
1313
1239
  right: 18px;
1314
1240
  z-index: 2;
1241
+ color: #ffffff;
1315
1242
  }
1316
1243
 
1317
1244
  .preview-title {
1318
1245
  margin: 0 0 12px;
1319
1246
  color: #ffffff;
1247
+ font-family: var(--font-sans);
1320
1248
  font-size: 18px;
1321
- font-weight: 700;
1249
+ font-weight: 800;
1322
1250
  }
1323
1251
 
1324
1252
  .pass-preview {
@@ -1408,10 +1336,6 @@ function showMessage(text: string, color: string) {
1408
1336
  }
1409
1337
 
1410
1338
  @media (max-width: 700px) {
1411
- .config-body {
1412
- padding: 22px 16px;
1413
- }
1414
-
1415
1339
  .online-mode-row,
1416
1340
  .printer-grid,
1417
1341
  .template-grid,