@visns-studio/visns-components 6.6.4 → 6.15.0

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.
Files changed (97) hide show
  1. package/README.md +1623 -18
  2. package/package.json +10 -4
  3. package/src/components/DataGrid.jsx +171 -37
  4. package/src/components/Fetch.jsx +80 -3
  5. package/src/components/Form.jsx +9 -0
  6. package/src/components/Navigation.jsx +109 -50
  7. package/src/components/Notification.jsx +279 -9
  8. package/src/components/TableFilter.jsx +14 -1
  9. package/src/components/auth/AuthBrandPanel.jsx +43 -0
  10. package/src/components/auth/AuthLoading.jsx +78 -0
  11. package/src/components/auth/AuthShell.jsx +59 -0
  12. package/src/components/auth/ClientAuth.jsx +161 -0
  13. package/src/components/auth/ClientAuthFrame.jsx +59 -0
  14. package/src/components/auth/ClientLogin.jsx +266 -55
  15. package/src/components/auth/ClientOTPVerify.jsx +587 -115
  16. package/src/components/auth/ImpersonateGate.jsx +254 -0
  17. package/src/components/auth/Login.jsx +134 -41
  18. package/src/components/auth/LogoutScreen.jsx +112 -0
  19. package/src/components/auth/Reset.jsx +134 -77
  20. package/src/components/auth/TwoFactorAuth.jsx +475 -297
  21. package/src/components/auth/Verify.jsx +237 -126
  22. package/src/components/auth/authEndpoints.js +105 -0
  23. package/src/components/auth/authFont.js +23 -0
  24. package/src/components/auth/authHelpers.js +465 -0
  25. package/src/components/auth/clientAuthProtocols.js +240 -0
  26. package/src/components/auth/useOptionalRouter.js +49 -0
  27. package/src/components/callQueue/CallQueuePop.jsx +1502 -0
  28. package/src/components/callQueue/CallQueueSettings.jsx +508 -0
  29. package/src/components/callQueue/callQueueHelpers.js +280 -0
  30. package/src/components/callQueue/callQueueSettingsHelpers.js +75 -0
  31. package/src/components/columns/ColumnRenderers.jsx +53 -7
  32. package/src/components/generic/GenericAuth.jsx +163 -96
  33. package/src/components/generic/GenericDetail.jsx +215 -90
  34. package/src/components/generic/GenericIndex.jsx +20 -47
  35. package/src/components/generic/GenericMain.jsx +5 -0
  36. package/src/components/generic/GroupedReportRenderer.jsx +1 -5
  37. package/src/components/generic/StandardModal.jsx +17 -5
  38. package/src/components/generic/reportSemanticSteps/SemanticEntityStep.jsx +1 -6
  39. package/src/components/generic/reportSemanticSteps/SemanticFieldsStep.jsx +4 -11
  40. package/src/components/generic/reportSemanticSteps/SemanticFiltersStep.jsx +9 -27
  41. package/src/components/generic/reportSemanticSteps/SemanticGroupingStep.jsx +5 -13
  42. package/src/components/generic/reportSemanticSteps/SemanticParameterPrompt.jsx +5 -13
  43. package/src/components/generic/reportSemanticSteps/SemanticPreviewStep.jsx +14 -31
  44. package/src/components/generic/reportSemanticSteps/SemanticRelationsStep.jsx +3 -9
  45. package/src/components/generic/reportSemanticSteps/SemanticValueInput.jsx +2 -15
  46. package/src/components/navActive.js +60 -0
  47. package/src/components/notify/desktopNotifications.js +256 -0
  48. package/src/components/sketch/SketchField.jsx +12 -2
  49. package/src/components/sms/SmsComposeModal.jsx +495 -0
  50. package/src/components/sms/SmsInbox.jsx +596 -0
  51. package/src/components/sms/SmsInboxBadge.jsx +468 -0
  52. package/src/components/sms/SmsLineSettings.jsx +854 -0
  53. package/src/components/sms/SmsThreadPanel.jsx +1184 -0
  54. package/src/components/sms/smsEndpoints.js +56 -0
  55. package/src/components/sms/smsHelpers.js +1075 -0
  56. package/src/components/sms/smsLiveState.js +132 -0
  57. package/src/components/sms/useSmsLive.js +307 -0
  58. package/src/components/styles/CallQueuePop.module.scss +646 -0
  59. package/src/components/styles/CallQueueSettings.module.scss +460 -0
  60. package/src/components/styles/ClientAuth.module.scss +229 -0
  61. package/src/components/styles/GenericDetail.module.scss +153 -26
  62. package/src/components/styles/GenericIndex.module.scss +13 -0
  63. package/src/components/styles/GenericMain.module.scss +19 -2
  64. package/src/components/styles/ImpersonateGate.module.scss +85 -0
  65. package/src/components/styles/Login.module.scss +13 -122
  66. package/src/components/styles/LogoutScreen.module.scss +89 -0
  67. package/src/components/styles/Navigation.module.scss +509 -223
  68. package/src/components/styles/Notification.module.scss +103 -0
  69. package/src/components/styles/Reset.module.scss +52 -186
  70. package/src/components/styles/Sms.module.scss +2382 -0
  71. package/src/components/styles/TableFilter.module.scss +118 -98
  72. package/src/components/styles/TwoFactorAuth.module.scss +115 -176
  73. package/src/components/styles/Vault.module.scss +1983 -0
  74. package/src/components/styles/Verify.module.scss +57 -180
  75. package/src/components/styles/_authBrandPanel.scss +163 -0
  76. package/src/components/styles/_authShell.scss +369 -0
  77. package/src/components/utils/buildEnv.js +67 -0
  78. package/src/components/utils/displayValue.js +94 -0
  79. package/src/components/utils/useDensity.js +345 -9
  80. package/src/components/vault/OtpChip.jsx +208 -0
  81. package/src/components/vault/PasswordGenerator.jsx +145 -0
  82. package/src/components/vault/QrScanner.jsx +326 -0
  83. package/src/components/vault/VaultAccessLog.jsx +225 -0
  84. package/src/components/vault/VaultConfirmPanel.jsx +113 -0
  85. package/src/components/vault/VaultEntryForm.jsx +740 -0
  86. package/src/components/vault/VaultManager.jsx +1000 -0
  87. package/src/components/vault/VaultQuickSearch.jsx +681 -0
  88. package/src/components/vault/qrDecode.js +257 -0
  89. package/src/components/vault/useDebouncedValue.js +22 -0
  90. package/src/components/vault/useVaultReveal.js +160 -0
  91. package/src/components/vault/vaultClipboard.js +33 -0
  92. package/src/components/vault/vaultEndpoints.js +40 -0
  93. package/src/components/vault/vaultFit.js +116 -0
  94. package/src/components/vault/vaultHelpers.js +547 -0
  95. package/src/components/vault/vaultNavigation.jsx +58 -0
  96. package/src/components/vault/vaultOtp.js +105 -0
  97. package/src/index.js +233 -1
@@ -0,0 +1,646 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Call Queue Pop (.cqpop)
3
+ // ---------------------------------------------------------------------------
4
+ // Bottom-right stack of "a call is ringing on this queue" cards, rendered once
5
+ // at the authenticated root by components/callQueue/CallQueuePop.jsx. Every
6
+ // call queue in the Zoom account pops and they all share one card style — the
7
+ // badge carries the queue's own name, so no per-queue colour is needed.
8
+ //
9
+ // Visually this is a glassmorphism language (blur + a translucent primary
10
+ // wash). It lives in its own namespace on purpose: a consuming app's legacy
11
+ // call banner is typically a single full-width bottom-centre bar, this is a
12
+ // vertical stack of independent cards. Nothing here touches that.
13
+ //
14
+ // Ported from the CRM's resources/css/_call-queue.scss. The sass `config`
15
+ // variables it used do not exist in this library, so every brand value is a
16
+ // CSS custom property with a neutral fallback — a project that defines nothing
17
+ // gets a plain, working call pop rather than any one project's colours.
18
+ // ---------------------------------------------------------------------------
19
+
20
+ // Local aliases only. Declared via a mixin and applied to BOTH the stack and
21
+ // the card: CSS-module scoping means nothing can rely on a shared `:root`
22
+ // block, and a card rendered outside the stack must still resolve its colours.
23
+ @mixin cqpop-tokens {
24
+ --cq-primary: var(--primary-color, #00386c);
25
+ --cq-secondary: var(--secondary-color, #404040);
26
+ --cq-tertiary: var(--tertiary-color, #fff);
27
+ --cq-paragraph: var(--paragraph-color, #2b2b2b);
28
+ --cq-background: var(--background-color, #fff);
29
+ --cq-br: var(--br, 5px);
30
+ --cq-btn-br: var(--btn-br, 5px);
31
+
32
+ // Literals in the original, kept literal here: they match the legacy
33
+ // `.cpview` / `.cpdismiss` actions rather than any token.
34
+ --cq-answer: rgb(43, 145, 85);
35
+ --cq-dismiss: rgb(214, 100, 100);
36
+ }
37
+
38
+ .cqpopStack {
39
+ @include cqpop-tokens;
40
+
41
+ position: fixed;
42
+ right: 1.25rem;
43
+ bottom: 1.25rem;
44
+ z-index: 1000; // above the goalbox (999), below the modals (1051+)
45
+ display: flex;
46
+ flex-direction: column;
47
+ align-items: stretch;
48
+ gap: 0.75rem;
49
+ width: 340px;
50
+ max-width: calc(100vw - 2rem);
51
+ // The stack itself is only a positioning frame — let clicks through the
52
+ // gaps between cards.
53
+ pointer-events: none;
54
+ }
55
+
56
+ // Trial-run banner above the stack: small, muted, unmistakably temporary.
57
+ .cqpopTrial {
58
+ pointer-events: none;
59
+ display: inline-flex;
60
+ gap: 0.3rem;
61
+ align-items: center;
62
+ align-self: flex-end;
63
+ padding: 0.2rem 0.55rem;
64
+ border: 1px solid color-mix(in srgb, var(--cq-primary) 20%, transparent);
65
+ border-radius: 999px;
66
+ background: color-mix(in srgb, var(--cq-primary) 8%, transparent);
67
+ backdrop-filter: blur(16px) saturate(180%);
68
+ -webkit-backdrop-filter: blur(16px) saturate(180%);
69
+ color: color-mix(in srgb, var(--cq-paragraph) 75%, transparent);
70
+ font-size: 0.68rem;
71
+ font-weight: 600;
72
+ letter-spacing: 0.2px;
73
+
74
+ svg {
75
+ flex-shrink: 0;
76
+ color: var(--cq-primary);
77
+ }
78
+ }
79
+
80
+ .cqpopCard {
81
+ @include cqpop-tokens;
82
+
83
+ pointer-events: auto;
84
+ padding: 0.75rem 0.9rem;
85
+ border: 1px solid color-mix(in srgb, var(--cq-primary) 25%, transparent);
86
+ border-radius: calc(var(--cq-br) * 2);
87
+ background-color: color-mix(in srgb, var(--cq-primary) 15%, transparent);
88
+ backdrop-filter: blur(16px) saturate(180%);
89
+ -webkit-backdrop-filter: blur(16px) saturate(180%);
90
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.18);
91
+ animation: cqpop-in 0.28s cubic-bezier(0.33, 1, 0.68, 1) both;
92
+
93
+ // One accent for every queue: the badge names the queue, so the spine is
94
+ // only there to mark the card as a call pop.
95
+ border-left: 4px solid var(--cq-primary);
96
+ }
97
+
98
+ .cqpopCardLeaving {
99
+ animation: cqpop-out 0.22s cubic-bezier(0.33, 0, 0.68, 0) both;
100
+ }
101
+
102
+ .cqpopCardHead {
103
+ display: flex;
104
+ align-items: center;
105
+ justify-content: space-between;
106
+ gap: 0.5rem;
107
+ margin-bottom: 0.55rem;
108
+ }
109
+
110
+ .cqpopCardBody {
111
+ display: flex;
112
+ flex-direction: column;
113
+ gap: 0.25rem;
114
+ }
115
+
116
+ .cqpopCardActions {
117
+ display: flex;
118
+ gap: 0.5rem;
119
+ margin-top: 0.75rem;
120
+ }
121
+
122
+ // A slim row under the primary actions for the quieter "go somewhere else"
123
+ // link, so Pick up / Dismiss keep the whole action bar to themselves.
124
+ .cqpopCardMore {
125
+ display: flex;
126
+ justify-content: center;
127
+ margin-top: 0.4rem;
128
+ }
129
+
130
+ // One badge style for every call queue — the text IS the queue's own name, so
131
+ // nothing is left for a colour to say.
132
+ .cqpopBadge {
133
+ display: inline-flex;
134
+ align-items: center;
135
+ gap: 0.4rem;
136
+ min-width: 0;
137
+ padding: 0.2rem 0.6rem;
138
+ border-radius: 999px;
139
+ background: var(--cq-primary);
140
+ font-size: 0.7rem;
141
+ font-weight: 700;
142
+ letter-spacing: 0.4px;
143
+ text-transform: uppercase;
144
+ color: var(--cq-tertiary);
145
+ // Queue names come from Zoom admin and can be long; keep the badge on one
146
+ // line and let the timer beside it hold its place.
147
+ overflow: hidden;
148
+ text-overflow: ellipsis;
149
+ white-space: nowrap;
150
+ }
151
+
152
+ // Pulsing "still ringing" indicator inside the badge.
153
+ .cqpopDot {
154
+ width: 7px;
155
+ height: 7px;
156
+ flex-shrink: 0;
157
+ border-radius: 50%;
158
+ background: rgba(255, 255, 255, 0.95);
159
+ animation: cqpop-pulse 1.2s ease-in-out infinite;
160
+ }
161
+
162
+ .cqpopTimer {
163
+ font-size: 0.72rem;
164
+ font-weight: 600;
165
+ color: color-mix(in srgb, var(--cq-paragraph) 75%, transparent);
166
+ white-space: nowrap;
167
+ font-variant-numeric: tabular-nums;
168
+ }
169
+
170
+ .cqpopCaller {
171
+ display: flex;
172
+ align-items: center;
173
+ gap: 0.4rem;
174
+ color: var(--cq-primary);
175
+
176
+ svg {
177
+ flex-shrink: 0;
178
+ }
179
+ }
180
+
181
+ .cqpopNumber {
182
+ font-family: 'SFMono-Regular', 'Menlo', 'Consolas', monospace;
183
+ font-size: 1.02rem;
184
+ font-weight: 700;
185
+ letter-spacing: 0.5px;
186
+ color: var(--cq-primary);
187
+ }
188
+
189
+ // Which of the contact's four numbers the caller rang in on ("mobile", "work"
190
+ // …). Deliberately quiet — it annotates the number, it isn't a headline.
191
+ .cqpopMatched {
192
+ font-size: 0.64rem;
193
+ font-weight: 600;
194
+ letter-spacing: 0.4px;
195
+ text-transform: uppercase;
196
+ color: color-mix(in srgb, var(--cq-paragraph) 55%, transparent);
197
+ white-space: nowrap;
198
+ }
199
+
200
+ .cqpopName {
201
+ font-size: 0.85rem;
202
+ font-weight: 600;
203
+ color: var(--cq-paragraph);
204
+ }
205
+
206
+ .cqpopClient {
207
+ font-size: 0.78rem;
208
+ color: var(--cq-paragraph);
209
+ }
210
+
211
+ .cqpopClientNone {
212
+ font-style: italic;
213
+ color: color-mix(in srgb, var(--cq-paragraph) 55%, transparent);
214
+ }
215
+
216
+ // Client name + open-task badge, separated by a hairline from the caller
217
+ // line above it so the snapshot reads as its own block.
218
+ .cqpopClientHead {
219
+ display: flex;
220
+ align-items: baseline;
221
+ justify-content: space-between;
222
+ gap: 0.5rem;
223
+ margin-top: 0.15rem;
224
+ padding-top: 0.35rem;
225
+ border-top: 1px solid color-mix(in srgb, var(--cq-primary) 18%, transparent);
226
+ }
227
+
228
+ .cqpopClientLink {
229
+ font-size: 0.88rem;
230
+ font-weight: 700;
231
+ color: var(--cq-primary);
232
+ text-decoration: underline;
233
+
234
+ &:hover {
235
+ color: color-mix(in srgb, var(--cq-primary) 85%, #000);
236
+ }
237
+ }
238
+
239
+ .cqpopClientName {
240
+ font-size: 0.88rem;
241
+ font-weight: 700;
242
+ color: var(--cq-primary);
243
+ }
244
+
245
+ // Two columns: a fixed label gutter keeps every value on the same left
246
+ // edge, however short the label is.
247
+ .cqpopClientGrid {
248
+ display: grid;
249
+ grid-template-columns: 4.4rem 1fr;
250
+ gap: 0.1rem 0.5rem;
251
+ margin: 0.35rem 0 0;
252
+ font-size: 0.72rem;
253
+ line-height: 1.35;
254
+
255
+ dt {
256
+ margin: 0;
257
+ font-weight: 600;
258
+ color: color-mix(in srgb, var(--cq-paragraph) 60%, transparent);
259
+ }
260
+
261
+ dd {
262
+ margin: 0;
263
+ color: var(--cq-paragraph);
264
+ overflow-wrap: anywhere;
265
+ }
266
+ }
267
+
268
+ // The next meeting doubles as the way into the calendar — a link, but kept
269
+ // as quiet as the plain text it replaced until it is pointed at.
270
+ .cqpopClientEvent {
271
+ display: block;
272
+ margin-top: 0.3rem;
273
+ border-radius: var(--cq-br);
274
+ font-size: 0.72rem;
275
+ font-weight: 600;
276
+ color: color-mix(in srgb, var(--cq-paragraph) 85%, transparent);
277
+ text-decoration: none;
278
+ // Rendered as a <button> when the host app supplies an onOpenCalendar
279
+ // handler, so the button chrome has to be neutralised here.
280
+ padding: 0;
281
+ border: none;
282
+ background: none;
283
+ font-family: inherit;
284
+ text-align: left;
285
+ cursor: pointer;
286
+
287
+ &:hover,
288
+ &:focus-visible {
289
+ color: var(--cq-primary);
290
+ text-decoration: underline;
291
+ }
292
+
293
+ &:focus-visible {
294
+ outline: 2px solid color-mix(in srgb, var(--cq-primary) 45%, transparent);
295
+ outline-offset: 1px;
296
+ }
297
+ }
298
+
299
+ // The open-task count is a disclosure button: it expands the list below.
300
+ .cqpopClientTasks {
301
+ display: inline-flex;
302
+ flex-shrink: 0;
303
+ align-items: center;
304
+ gap: 0.25rem;
305
+ padding: 0.1rem 0.4rem;
306
+ border: 1px solid transparent;
307
+ border-radius: 999px;
308
+ background: color-mix(in srgb, var(--cq-dismiss) 18%, transparent);
309
+ color: color-mix(in srgb, var(--cq-dismiss) 70%, #000);
310
+ font-family: inherit;
311
+ font-size: 0.64rem;
312
+ font-weight: 700;
313
+ line-height: 1.4;
314
+ white-space: nowrap;
315
+ cursor: pointer;
316
+ transition:
317
+ background 0.15s ease,
318
+ border-color 0.15s ease;
319
+
320
+ svg {
321
+ flex-shrink: 0;
322
+ transition: transform 0.2s ease;
323
+ }
324
+
325
+ &:hover,
326
+ &:focus-visible {
327
+ background: color-mix(in srgb, var(--cq-dismiss) 30%, transparent);
328
+ border-color: color-mix(in srgb, var(--cq-dismiss) 55%, transparent);
329
+ }
330
+
331
+ &:focus-visible {
332
+ outline: 2px solid color-mix(in srgb, var(--cq-dismiss) 55%, transparent);
333
+ outline-offset: 1px;
334
+ }
335
+ }
336
+
337
+ .cqpopClientTasksOpen svg {
338
+ transform: rotate(180deg);
339
+ }
340
+
341
+ // The expansion behind the open-task chip. Capped and internally scrollable so
342
+ // a client with ten open tickets grows the card by a fixed, predictable amount
343
+ // rather than shoving the rest of the bottom-anchored stack off screen.
344
+ .cqpopTasks {
345
+ margin-top: 0.4rem;
346
+ padding-top: 0.4rem;
347
+ border-top: 1px solid color-mix(in srgb, var(--cq-primary) 18%, transparent);
348
+ overflow: hidden;
349
+ // `backwards`, not `both`: the max-height in the keyframes is only there to
350
+ // drive the reveal, it must not linger and clip the finished panel.
351
+ animation: cqpop-tasks-in 0.2s cubic-bezier(0.33, 1, 0.68, 1) backwards;
352
+ }
353
+
354
+ .cqpopTasksNote {
355
+ margin: 0;
356
+ font-size: 0.7rem;
357
+ font-style: italic;
358
+ color: color-mix(in srgb, var(--cq-paragraph) 55%, transparent);
359
+ }
360
+
361
+ .cqpopTasksList {
362
+ max-height: 160px;
363
+ margin: 0;
364
+ padding: 0;
365
+ overflow-y: auto;
366
+ list-style: none;
367
+ scrollbar-width: thin;
368
+
369
+ li + li {
370
+ border-top: 1px solid color-mix(in srgb, var(--cq-primary) 10%, transparent);
371
+ }
372
+ }
373
+
374
+ .cqpopTasksItem {
375
+ display: flex;
376
+ flex-direction: column;
377
+ gap: 0.05rem;
378
+ padding: 0.28rem 0.3rem;
379
+ border-radius: var(--cq-br);
380
+ color: var(--cq-paragraph);
381
+ text-decoration: none;
382
+ transition:
383
+ background 0.15s ease,
384
+ color 0.15s ease;
385
+
386
+ &:hover,
387
+ &:focus-visible {
388
+ background: color-mix(in srgb, var(--cq-primary) 12%, transparent);
389
+ color: var(--cq-primary);
390
+ }
391
+
392
+ &:focus-visible {
393
+ outline: 2px solid color-mix(in srgb, var(--cq-primary) 45%, transparent);
394
+ outline-offset: -1px;
395
+ }
396
+ }
397
+
398
+ .cqpopTasksLabel {
399
+ font-size: 0.72rem;
400
+ font-weight: 600;
401
+ line-height: 1.3;
402
+ overflow-wrap: anywhere;
403
+ }
404
+
405
+ .cqpopTasksMeta {
406
+ display: flex;
407
+ gap: 0.4rem;
408
+ font-size: 0.64rem;
409
+ color: color-mix(in srgb, var(--cq-paragraph) 60%, transparent);
410
+ }
411
+
412
+ .cqpopTasksType {
413
+ font-weight: 600;
414
+ letter-spacing: 0.3px;
415
+ text-transform: uppercase;
416
+ }
417
+
418
+ .cqpopTasksDue {
419
+ font-variant-numeric: tabular-nums;
420
+ white-space: nowrap;
421
+ }
422
+
423
+ .cqpopTasksAll {
424
+ display: inline-block;
425
+ margin-top: 0.35rem;
426
+ padding: 0.1rem 0.3rem;
427
+ border-radius: var(--cq-br);
428
+ font-size: 0.68rem;
429
+ font-weight: 700;
430
+ color: var(--cq-primary);
431
+ text-decoration: none;
432
+
433
+ &:hover,
434
+ &:focus-visible {
435
+ color: color-mix(in srgb, var(--cq-primary) 85%, #000);
436
+ text-decoration: underline;
437
+ }
438
+
439
+ &:focus-visible {
440
+ outline: 2px solid color-mix(in srgb, var(--cq-primary) 45%, transparent);
441
+ outline-offset: 1px;
442
+ }
443
+ }
444
+
445
+ // Tertiary action: a text button, deliberately not competing with .cqpopBtn.
446
+ .cqpopLink {
447
+ display: inline-flex;
448
+ align-items: center;
449
+ gap: 0.3rem;
450
+ padding: 0.2rem 0.45rem;
451
+ border-radius: var(--cq-btn-br);
452
+ font-size: 0.7rem;
453
+ font-weight: 600;
454
+ color: color-mix(in srgb, var(--cq-paragraph) 75%, transparent);
455
+ text-decoration: none;
456
+ transition: all 0.15s ease;
457
+
458
+ svg {
459
+ flex-shrink: 0;
460
+ }
461
+
462
+ &:hover,
463
+ &:focus-visible {
464
+ background: color-mix(in srgb, var(--cq-primary) 12%, transparent);
465
+ color: var(--cq-primary);
466
+ }
467
+
468
+ &:focus-visible {
469
+ outline: 2px solid color-mix(in srgb, var(--cq-primary) 45%, transparent);
470
+ outline-offset: 1px;
471
+ }
472
+ }
473
+
474
+ // Demo cards, and numbers that do not reduce to an AU local number, have no
475
+ // workspace to open — the row still renders so the card keeps its shape.
476
+ .cqpopLinkInert {
477
+ cursor: not-allowed;
478
+ opacity: 0.5;
479
+
480
+ &:hover {
481
+ background: transparent;
482
+ color: color-mix(in srgb, var(--cq-paragraph) 75%, transparent);
483
+ }
484
+ }
485
+
486
+ .cqpopBtn {
487
+ display: inline-flex;
488
+ flex: 1;
489
+ align-items: center;
490
+ justify-content: center;
491
+ gap: 0.35rem;
492
+ padding: 0.4rem 0.7rem;
493
+ border-radius: var(--cq-btn-br);
494
+ font-size: 0.8rem;
495
+ font-weight: 600;
496
+ line-height: 1.2;
497
+ cursor: pointer;
498
+ text-decoration: none;
499
+ transition: all 0.15s ease;
500
+
501
+ svg {
502
+ flex-shrink: 0;
503
+ }
504
+
505
+ &:hover {
506
+ transform: translateY(-1px);
507
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.18);
508
+ }
509
+
510
+ &:active {
511
+ transform: translateY(0);
512
+ box-shadow: none;
513
+ }
514
+ }
515
+
516
+ // Green, matching the legacy `.cpview` action.
517
+ .cqpopBtnAnswer {
518
+ border: 1px solid color-mix(in srgb, var(--cq-answer) 92%, #000);
519
+ background: var(--cq-answer);
520
+ color: var(--cq-tertiary);
521
+
522
+ &:hover {
523
+ background: color-mix(in srgb, var(--cq-answer) 88%, #000);
524
+ color: var(--cq-tertiary);
525
+ }
526
+ }
527
+
528
+ // Pick up while the dial mechanism is unverified: present so staff learn
529
+ // where it will live, but visibly inert.
530
+ .cqpopBtnSoon {
531
+ border: 1px solid color-mix(in srgb, var(--cq-answer) 35%, transparent);
532
+ background: color-mix(in srgb, var(--cq-answer) 25%, transparent);
533
+ color: rgba(255, 255, 255, 0.75);
534
+ cursor: not-allowed;
535
+
536
+ &:hover {
537
+ transform: none;
538
+ box-shadow: none;
539
+ background: color-mix(in srgb, var(--cq-answer) 25%, transparent);
540
+ }
541
+ }
542
+
543
+ // Red outline, a quieter take on the legacy `.cpdismiss`.
544
+ .cqpopBtnDismiss {
545
+ flex: 0 0 auto;
546
+ border: 1px solid color-mix(in srgb, var(--cq-dismiss) 85%, #000);
547
+ background: transparent;
548
+ color: color-mix(in srgb, var(--cq-dismiss) 78%, #000);
549
+
550
+ // A queue with no pickup code renders Dismiss on its own — then it
551
+ // takes the whole row rather than sitting stranded on the left.
552
+ &:only-child {
553
+ flex: 1;
554
+ }
555
+
556
+ &:hover {
557
+ background: color-mix(in srgb, var(--cq-dismiss) 85%, #000);
558
+ color: var(--cq-tertiary);
559
+ }
560
+ }
561
+
562
+ @keyframes cqpop-in {
563
+ from {
564
+ opacity: 0;
565
+ transform: translateY(24px) scale(0.97);
566
+ }
567
+
568
+ to {
569
+ opacity: 1;
570
+ transform: translateY(0) scale(1);
571
+ }
572
+ }
573
+
574
+ @keyframes cqpop-out {
575
+ from {
576
+ opacity: 1;
577
+ transform: translateY(0) scale(1);
578
+ }
579
+
580
+ to {
581
+ opacity: 0;
582
+ transform: translateY(12px) scale(0.97);
583
+ }
584
+ }
585
+
586
+ @keyframes cqpop-tasks-in {
587
+ from {
588
+ opacity: 0;
589
+ max-height: 0;
590
+ transform: translateY(-4px);
591
+ }
592
+
593
+ to {
594
+ opacity: 1;
595
+ max-height: 300px;
596
+ transform: translateY(0);
597
+ }
598
+ }
599
+
600
+ @keyframes cqpop-pulse {
601
+ 0%,
602
+ 100% {
603
+ opacity: 1;
604
+ transform: scale(1);
605
+ }
606
+
607
+ 50% {
608
+ opacity: 0.35;
609
+ transform: scale(0.7);
610
+ }
611
+ }
612
+
613
+ // Full-width cards on small screens, same 768px hinge the goalbox uses.
614
+ @media (max-width: 768px) {
615
+ .cqpopStack {
616
+ right: 0.5rem;
617
+ bottom: 0.5rem;
618
+ left: 0.5rem;
619
+ width: auto;
620
+ max-width: none;
621
+ }
622
+
623
+ .cqpopCardActions {
624
+ .cqpopBtn {
625
+ flex: 1;
626
+ }
627
+ }
628
+ }
629
+
630
+ // Respect a reduced-motion preference: keep the card, drop the movement.
631
+ @media (prefers-reduced-motion: reduce) {
632
+ .cqpopCard,
633
+ .cqpopCardLeaving,
634
+ .cqpopDot,
635
+ .cqpopTasks {
636
+ animation: none;
637
+ }
638
+
639
+ .cqpopBtn:hover {
640
+ transform: none;
641
+ }
642
+
643
+ .cqpopClientTasks svg {
644
+ transition: none;
645
+ }
646
+ }