@striae-org/striae 3.0.4 → 3.1.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 (91) hide show
  1. package/NOTICE +0 -5
  2. package/app/components/actions/case-export/core-export.ts +1 -1
  3. package/app/components/actions/case-export/download-handlers.ts +10 -12
  4. package/app/components/actions/case-export/metadata-helpers.ts +1 -1
  5. package/app/components/actions/case-import/confirmation-import.ts +24 -9
  6. package/app/components/actions/case-import/orchestrator.ts +3 -4
  7. package/app/components/actions/case-import/validation.ts +3 -3
  8. package/app/components/actions/case-import/zip-processing.ts +12 -48
  9. package/app/components/actions/case-manage.ts +0 -1
  10. package/app/components/actions/confirm-export.ts +2 -2
  11. package/app/components/audit/user-audit-viewer.tsx +53 -15
  12. package/app/components/audit/user-audit.module.css +11 -4
  13. package/app/components/canvas/box-annotations/box-annotations.tsx +36 -7
  14. package/app/components/canvas/canvas.tsx +35 -24
  15. package/app/components/canvas/confirmation/confirmation.module.css +5 -2
  16. package/app/components/canvas/confirmation/confirmation.tsx +25 -8
  17. package/app/components/sidebar/case-export/case-export.module.css +194 -5
  18. package/app/components/sidebar/case-export/case-export.tsx +291 -11
  19. package/app/components/sidebar/case-import/case-import.module.css +9 -5
  20. package/app/components/sidebar/case-import/case-import.tsx +30 -7
  21. package/app/components/sidebar/case-import/components/CasePreviewSection.tsx +2 -2
  22. package/app/components/sidebar/case-import/components/ConfirmationDialog.tsx +1 -1
  23. package/app/components/sidebar/case-import/components/ExistingCaseSection.tsx +1 -1
  24. package/app/components/sidebar/case-import/hooks/useFilePreview.ts +34 -9
  25. package/app/components/sidebar/cases/case-sidebar.tsx +13 -13
  26. package/app/components/sidebar/cases/cases-modal.tsx +12 -2
  27. package/app/components/sidebar/files/files-modal.tsx +28 -8
  28. package/app/components/sidebar/sidebar.module.css +2 -3
  29. package/app/components/sidebar/sidebar.tsx +1 -16
  30. package/app/components/sidebar/upload/image-upload-zone.tsx +4 -4
  31. package/app/components/toolbar/toolbar-color-selector.module.css +2 -2
  32. package/app/components/toolbar/toolbar-color-selector.tsx +3 -3
  33. package/app/components/toolbar/toolbar.tsx +19 -9
  34. package/app/components/user/delete-account.module.css +4 -1
  35. package/app/components/user/delete-account.tsx +22 -3
  36. package/app/components/user/manage-profile.tsx +0 -2
  37. package/app/entry.server.tsx +2 -3
  38. package/app/hooks/useInactivityTimeout.ts +5 -1
  39. package/app/root.tsx +0 -3
  40. package/app/routes/_index.tsx +1 -16
  41. package/app/routes/auth/emailVerification.tsx +1 -1
  42. package/app/routes/auth/login.tsx +7 -5
  43. package/app/routes/auth/route.ts +3 -12
  44. package/app/routes/striae/striae.tsx +1 -1
  45. package/app/services/audit.service.ts +29 -9
  46. package/app/tailwind.css +16 -1
  47. package/app/types/audit.ts +3 -3
  48. package/app/types/case.ts +1 -1
  49. package/app/types/import.ts +0 -2
  50. package/app/utils/SHA256.ts +3 -3
  51. package/app/utils/batch-operations.ts +6 -6
  52. package/app/utils/data-operations.ts +14 -7
  53. package/app/utils/permissions.ts +0 -2
  54. package/functions/[[path]].ts +0 -1
  55. package/package.json +1 -2
  56. package/public/_headers +0 -12
  57. package/public/assets/striae.jpg +0 -0
  58. package/scripts/deploy-config.sh +0 -7
  59. package/scripts/run-eslint.cjs +14 -6
  60. package/worker-configuration.d.ts +2 -2
  61. package/workers/audit-worker/src/audit-worker.example.ts +9 -7
  62. package/workers/audit-worker/worker-configuration.d.ts +2 -2
  63. package/workers/audit-worker/wrangler.jsonc.example +1 -1
  64. package/workers/data-worker/src/data-worker.example.ts +1 -1
  65. package/workers/data-worker/worker-configuration.d.ts +2 -2
  66. package/workers/data-worker/wrangler.jsonc.example +1 -1
  67. package/workers/image-worker/worker-configuration.d.ts +2 -2
  68. package/workers/image-worker/wrangler.jsonc.example +1 -1
  69. package/workers/keys-worker/worker-configuration.d.ts +2 -2
  70. package/workers/keys-worker/wrangler.jsonc.example +1 -1
  71. package/workers/pdf-worker/src/pdf-worker.example.ts +3 -3
  72. package/workers/pdf-worker/worker-configuration.d.ts +7448 -7448
  73. package/workers/pdf-worker/wrangler.jsonc.example +1 -1
  74. package/workers/user-worker/src/user-worker.example.ts +10 -10
  75. package/workers/user-worker/worker-configuration.d.ts +2 -2
  76. package/workers/user-worker/wrangler.jsonc.example +1 -1
  77. package/wrangler.toml.example +1 -1
  78. package/app/components/sidebar/hash/hash-utility.module.css +0 -366
  79. package/app/components/sidebar/hash/hash-utility.tsx +0 -982
  80. package/app/config-example/meta-config.json +0 -6
  81. package/app/routes/mobile-prevented/mobilePrevented.module.css +0 -47
  82. package/app/routes/mobile-prevented/mobilePrevented.tsx +0 -26
  83. package/app/routes/mobile-prevented/route.ts +0 -14
  84. package/app/utils/device-detection.ts +0 -5
  85. package/app/utils/html-sanitizer.ts +0 -80
  86. package/app/utils/meta.ts +0 -48
  87. package/public/icon-256.png +0 -0
  88. package/public/icon-512.png +0 -0
  89. package/public/manifest.json +0 -25
  90. package/public/shortcut.png +0 -0
  91. package/public/social-image.png +0 -0
@@ -111,7 +111,7 @@ export const BoxAnnotations = ({
111
111
  x: Math.max(0, Math.min(100, x)),
112
112
  y: Math.max(0, Math.min(100, y))
113
113
  };
114
- } catch (error) {
114
+ } catch {
115
115
  return { x: 0, y: 0 };
116
116
  }
117
117
  }, [imageRef]);
@@ -143,7 +143,7 @@ export const BoxAnnotations = ({
143
143
  x: Math.max(DIALOG_OFFSET, adjustedX),
144
144
  y: Math.max(DIALOG_OFFSET, adjustedY)
145
145
  };
146
- }, []);
146
+ }, [imageRef]);
147
147
 
148
148
  // Handle mouse down - start drawing
149
149
  const handleMouseDown = useCallback((e: React.MouseEvent) => {
@@ -173,7 +173,7 @@ export const BoxAnnotations = ({
173
173
  currentY: y
174
174
  });
175
175
  }
176
- }, [isAnnotationMode, getRelativeCoordinates]);
176
+ }, [isReadOnly, isAnnotationMode, imageRef, getRelativeCoordinates]);
177
177
 
178
178
  // Handle mouse move - update current drawing box
179
179
  const handleMouseMove = useCallback((e: React.MouseEvent) => {
@@ -281,9 +281,14 @@ export const BoxAnnotations = ({
281
281
  annotationColor,
282
282
  annotations,
283
283
  onAnnotationsChange,
284
+ annotationData,
285
+ onAnnotationDataChange,
284
286
  generateAnnotationId,
285
287
  calculateDialogPosition,
286
- user
288
+ user,
289
+ caseNumber,
290
+ imageFileId,
291
+ originalImageFileName
287
292
  ]);
288
293
 
289
294
  // Remove a box annotation with validation
@@ -364,7 +369,16 @@ export const BoxAnnotations = ({
364
369
  console.error('Failed to remove annotation or log audit:', error);
365
370
  // Continue with removal even if audit logging fails
366
371
  }
367
- }, [annotations, onAnnotationsChange, annotationData, onAnnotationDataChange, user]);
372
+ }, [
373
+ annotations,
374
+ onAnnotationsChange,
375
+ annotationData,
376
+ onAnnotationDataChange,
377
+ user,
378
+ caseNumber,
379
+ imageFileId,
380
+ originalImageFileName
381
+ ]);
368
382
 
369
383
  // Handle right-click to remove annotation
370
384
  const handleAnnotationRightClick = useCallback((e: React.MouseEvent, annotationId: string) => {
@@ -461,7 +475,17 @@ export const BoxAnnotations = ({
461
475
  // Still try to close dialog even if update fails
462
476
  setLabelDialog({ isVisible: false, annotationId: null, x: 0, y: 0, label: '' });
463
477
  }
464
- }, [labelDialog, annotations, onAnnotationsChange, annotationData, onAnnotationDataChange, user]);
478
+ }, [
479
+ labelDialog,
480
+ annotations,
481
+ onAnnotationsChange,
482
+ annotationData,
483
+ onAnnotationDataChange,
484
+ user,
485
+ caseNumber,
486
+ imageFileId,
487
+ originalImageFileName
488
+ ]);
465
489
 
466
490
  // Handle label cancellation
467
491
  const handleLabelCancel = useCallback(() => {
@@ -584,7 +608,6 @@ export const BoxAnnotations = ({
584
608
  onKeyDown={handleLabelKeyDown}
585
609
  placeholder="Enter label..."
586
610
  className={styles.labelInput}
587
- autoFocus
588
611
  />
589
612
  <div className={styles.labelDialogButtons}>
590
613
  <button
@@ -620,6 +643,12 @@ export const BoxAnnotations = ({
620
643
  onMouseMove={handleMouseMove}
621
644
  onMouseUp={handleMouseUp}
622
645
  onMouseLeave={handleMouseUp}
646
+ onKeyDown={() => {
647
+ // Keyboard interactions are handled by parent canvas tools.
648
+ }}
649
+ role="button"
650
+ tabIndex={0}
651
+ aria-label="Box annotation canvas"
623
652
  style={{
624
653
  cursor: isAnnotationMode && !isReadOnly ? 'crosshair' : 'default',
625
654
  pointerEvents: 'auto' // Always allow pointer events for viewing annotations
@@ -1,10 +1,9 @@
1
- import { useEffect, useState, useRef, useContext } from 'react';
1
+ import { useEffect, useState, useRef, useContext, useCallback } from 'react';
2
2
  import { BoxAnnotations } from './box-annotations/box-annotations';
3
3
  import { ConfirmationModal } from './confirmation/confirmation';
4
4
  import { AnnotationData, BoxAnnotation, ConfirmationData } from '~/types/annotations';
5
5
  import { AuthContext } from '~/contexts/auth.context';
6
6
  import { storeConfirmation } from '~/components/actions/confirm-export';
7
- import { auditService } from '~/services/audit.service';
8
7
  import styles from './canvas.module.css';
9
8
 
10
9
  interface CanvasProps {
@@ -51,6 +50,15 @@ export const Canvas = ({
51
50
  const [isConfirmationModalOpen, setIsConfirmationModalOpen] = useState(false);
52
51
  const imageRef = useRef<HTMLImageElement>(null);
53
52
 
53
+ const resetImageLoadState = useCallback(() => {
54
+ setLoadError(undefined);
55
+ setIsLoading(false);
56
+ }, []);
57
+
58
+ const clearFlashingState = useCallback(() => {
59
+ setIsFlashing(false);
60
+ }, []);
61
+
54
62
  // Handle box annotation changes
55
63
  const handleBoxAnnotationsChange = (boxAnnotations: BoxAnnotation[]) => {
56
64
  if (!onAnnotationUpdate || !annotationData || isReadOnly || annotationData.confirmationData) return;
@@ -114,13 +122,19 @@ export const Canvas = ({
114
122
 
115
123
  useEffect(() => {
116
124
  if (!imageUrl) {
117
- setLoadError(undefined);
118
- setIsLoading(false);
119
- return;
125
+ const resetTimer = window.setTimeout(() => {
126
+ resetImageLoadState();
127
+ }, 0);
128
+
129
+ return () => {
130
+ window.clearTimeout(resetTimer);
131
+ };
120
132
  }
121
133
 
122
- setIsLoading(true);
123
- setLoadError(undefined);
134
+ const startLoadingTimer = window.setTimeout(() => {
135
+ setIsLoading(true);
136
+ setLoadError(undefined);
137
+ }, 0);
124
138
 
125
139
  const img = new Image();
126
140
 
@@ -129,36 +143,33 @@ export const Canvas = ({
129
143
  setLoadError(undefined);
130
144
  };
131
145
 
132
- img.onerror = (e) => {
146
+ img.onerror = () => {
133
147
  setLoadError({
134
148
  type: 'load',
135
- message: `Failed to load image: ${e instanceof Error ? e.message : 'Unknown error'}`
149
+ message: 'Failed to load image: Unknown error'
136
150
  });
137
151
  setIsLoading(false);
138
152
  };
139
153
 
140
- try {
141
- img.src = imageUrl;
142
- } catch (e) {
143
- setLoadError({
144
- type: 'invalid',
145
- message: 'Invalid image URL provided'
146
- });
147
- setIsLoading(false);
148
- }
154
+ img.src = imageUrl;
149
155
 
150
156
  return () => {
157
+ window.clearTimeout(startLoadingTimer);
151
158
  img.onload = null;
152
159
  img.onerror = null;
153
- setLoadError(undefined);
154
- setIsLoading(false);
160
+ resetImageLoadState();
155
161
  };
156
- }, [imageUrl]);
162
+ }, [imageUrl, resetImageLoadState]);
157
163
 
158
164
  useEffect(() => {
159
165
  if (!activeAnnotations?.has('class') || !annotationData?.hasSubclass) {
160
- setIsFlashing(false);
161
- return;
166
+ const flashResetTimer = window.setTimeout(() => {
167
+ clearFlashingState();
168
+ }, 0);
169
+
170
+ return () => {
171
+ window.clearTimeout(flashResetTimer);
172
+ };
162
173
  }
163
174
 
164
175
  const flashInterval = setInterval(() => {
@@ -171,7 +182,7 @@ export const Canvas = ({
171
182
  }, 60000);
172
183
 
173
184
  return () => clearInterval(flashInterval);
174
- }, [activeAnnotations, annotationData?.hasSubclass]);
185
+ }, [activeAnnotations, annotationData?.hasSubclass, clearFlashingState]);
175
186
 
176
187
  const getErrorMessage = () => {
177
188
  if (error) return error;
@@ -6,6 +6,7 @@
6
6
  justify-content: center;
7
7
  align-items: center;
8
8
  z-index: var(--zIndex5);
9
+ cursor: default;
9
10
  transition: background-color var(--durationM) var(--bezierFastoutSlowin);
10
11
  }
11
12
 
@@ -15,7 +16,9 @@
15
16
  width: 90%;
16
17
  max-width: 400px;
17
18
  max-height: 90vh;
18
- box-shadow: 0 var(--spaceXS) var(--spaceL) color-mix(in lab, var(--black) 10%, transparent);
19
+ box-shadow: 0 var(--spaceXS) var(--spaceL)
20
+ color-mix(in lab, var(--black) 10%, transparent);
21
+ cursor: default;
19
22
  transition: background-color var(--durationM) var(--bezierFastoutSlowin);
20
23
  }
21
24
 
@@ -106,7 +109,7 @@
106
109
  border-radius: var(--spaceXS);
107
110
  font-size: var(--fontSizeBodyXS);
108
111
  color: var(--text);
109
- font-family: 'Inter', monospace;
112
+ font-family: "Inter", monospace;
110
113
  }
111
114
 
112
115
  .error {
@@ -104,14 +104,32 @@ export const ConfirmationModal = ({ isOpen, onClose, onConfirm, company, existin
104
104
  }
105
105
  };
106
106
 
107
- const handleOverlayClick = (e: React.MouseEvent) => {
107
+ const handleOverlayMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
108
108
  if (e.target === e.currentTarget) {
109
109
  onClose();
110
110
  }
111
111
  };
112
112
 
113
+ const handleOverlayKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
114
+ if (e.target !== e.currentTarget) {
115
+ return;
116
+ }
117
+
118
+ if (e.key === 'Enter' || e.key === ' ') {
119
+ e.preventDefault();
120
+ onClose();
121
+ }
122
+ };
123
+
113
124
  return (
114
- <div className={styles.overlay} onClick={handleOverlayClick}>
125
+ <div
126
+ className={styles.overlay}
127
+ onMouseDown={handleOverlayMouseDown}
128
+ onKeyDown={handleOverlayKeyDown}
129
+ role="button"
130
+ tabIndex={0}
131
+ aria-label="Close confirmation dialog"
132
+ >
115
133
  <div className={styles.modal}>
116
134
  <div className={styles.header}>
117
135
  <h2 className={styles.title}>
@@ -135,7 +153,7 @@ export const ConfirmationModal = ({ isOpen, onClose, onConfirm, company, existin
135
153
  <div className={styles.content}>
136
154
  <div className={styles.fieldGroup}>
137
155
  <div className={styles.field}>
138
- <label className={styles.label}>Name:</label>
156
+ <span className={styles.label}>Name:</span>
139
157
  <div className={styles.readOnlyValue}>
140
158
  {hasExistingConfirmation ? existingConfirmation.fullName : fullName}
141
159
  </div>
@@ -154,33 +172,32 @@ export const ConfirmationModal = ({ isOpen, onClose, onConfirm, company, existin
154
172
  }}
155
173
  placeholder="Enter your badge or ID number"
156
174
  disabled={isConfirming || hasExistingConfirmation}
157
- autoFocus={!hasExistingConfirmation}
158
175
  />
159
176
  </div>
160
177
 
161
178
  <div className={styles.field}>
162
- <label className={styles.label}>Email:</label>
179
+ <span className={styles.label}>Email:</span>
163
180
  <div className={styles.readOnlyValue}>
164
181
  {hasExistingConfirmation ? existingConfirmation.confirmedByEmail : userEmail}
165
182
  </div>
166
183
  </div>
167
184
 
168
185
  <div className={styles.field}>
169
- <label className={styles.label}>Lab/Company:</label>
186
+ <span className={styles.label}>Lab/Company:</span>
170
187
  <div className={styles.readOnlyValue}>
171
188
  {hasExistingConfirmation ? existingConfirmation.confirmedByCompany : labCompany}
172
189
  </div>
173
190
  </div>
174
191
 
175
192
  <div className={styles.field}>
176
- <label className={styles.label}>Timestamp:</label>
193
+ <span className={styles.label}>Timestamp:</span>
177
194
  <div className={styles.readOnlyValue}>
178
195
  {hasExistingConfirmation ? existingConfirmation.timestamp : timestamp}
179
196
  </div>
180
197
  </div>
181
198
 
182
199
  <div className={styles.field}>
183
- <label className={styles.label}>Confirmation ID:</label>
200
+ <span className={styles.label}>Confirmation ID:</span>
184
201
  <div className={styles.readOnlyValue}>
185
202
  {hasExistingConfirmation ? existingConfirmation.confirmationId : confirmationId}
186
203
  </div>
@@ -6,6 +6,7 @@
6
6
  justify-content: center;
7
7
  align-items: center;
8
8
  z-index: var(--zIndex5);
9
+ cursor: default;
9
10
  transition: background-color var(--durationM) var(--bezierFastoutSlowin);
10
11
  }
11
12
 
@@ -15,7 +16,12 @@
15
16
  width: 90%;
16
17
  max-width: 480px;
17
18
  max-height: 90vh;
18
- box-shadow: 0 var(--spaceXS) var(--spaceL) color-mix(in lab, var(--black) 10%, transparent);
19
+ display: flex;
20
+ flex-direction: column;
21
+ overflow: hidden;
22
+ box-shadow: 0 var(--spaceXS) var(--spaceL)
23
+ color-mix(in lab, var(--black) 10%, transparent);
24
+ cursor: default;
19
25
  transition: background-color var(--durationM) var(--bezierFastoutSlowin);
20
26
  }
21
27
 
@@ -52,8 +58,10 @@
52
58
 
53
59
  .content {
54
60
  padding: var(--spaceL);
61
+ flex: 1 1 auto;
62
+ min-height: 0;
55
63
  overflow-y: auto;
56
- max-height: calc(90vh - var(--space3XL));
64
+ overflow-x: hidden;
57
65
  }
58
66
 
59
67
  .formatSelector {
@@ -154,7 +162,7 @@
154
162
  }
155
163
 
156
164
  .checkbox:checked::after {
157
- content: '';
165
+ content: "";
158
166
  position: absolute;
159
167
  left: 3px;
160
168
  top: 0px;
@@ -191,6 +199,7 @@
191
199
  }
192
200
 
193
201
  .checkboxTooltip {
202
+ display: block;
194
203
  font-size: var(--fontSizeBodyXS);
195
204
  color: var(--textBody);
196
205
  margin: 0;
@@ -286,6 +295,186 @@
286
295
  box-shadow: none;
287
296
  }
288
297
 
298
+ .publicKeySection {
299
+ margin-top: var(--spaceS);
300
+ }
301
+
302
+ .publicKeyButton {
303
+ width: 100%;
304
+ background: transparent;
305
+ color: var(--primary);
306
+ border: 1px solid color-mix(in lab, var(--primary) 35%, transparent);
307
+ border-radius: var(--spaceXS);
308
+ padding: var(--spaceS) var(--spaceM);
309
+ font-size: var(--fontSizeBodyS);
310
+ font-weight: var(--fontWeightMedium);
311
+ cursor: pointer;
312
+ transition: all var(--durationS) var(--bezierFastoutSlowin);
313
+ }
314
+
315
+ .publicKeyButton:hover {
316
+ background: color-mix(in lab, var(--primary) 10%, transparent);
317
+ border-color: color-mix(in lab, var(--primary) 55%, transparent);
318
+ }
319
+
320
+ .publicKeyOverlay {
321
+ position: fixed;
322
+ inset: 0;
323
+ background-color: color-mix(in lab, var(--background) 60%, transparent);
324
+ display: flex;
325
+ justify-content: center;
326
+ align-items: center;
327
+ z-index: var(--zIndex5);
328
+ padding: var(--spaceL);
329
+ }
330
+
331
+ .publicKeyModal {
332
+ width: 100%;
333
+ max-width: 640px;
334
+ max-height: 90vw;
335
+ background: var(--backgroundLight);
336
+ border-radius: var(--spaceXS);
337
+ display: flex;
338
+ flex-direction: column;
339
+ box-shadow: 0 var(--spaceXS) var(--spaceL)
340
+ color-mix(in lab, var(--black) 18%, transparent);
341
+ overflow: hidden;
342
+ cursor: default;
343
+ }
344
+
345
+ .publicKeyHeader {
346
+ display: flex;
347
+ justify-content: space-between;
348
+ align-items: center;
349
+ padding: var(--spaceL);
350
+ border-bottom: 1px solid color-mix(in lab, var(--text) 10%, transparent);
351
+ }
352
+
353
+ .publicKeyTitle {
354
+ margin: 0;
355
+ font-size: var(--fontSizeBodyL);
356
+ font-weight: 600;
357
+ color: var(--textTitle);
358
+ }
359
+
360
+ .publicKeyContent {
361
+ padding: var(--spaceL);
362
+ flex: 1 1 auto;
363
+ min-height: 0;
364
+ display: flex;
365
+ flex-direction: column;
366
+ gap: var(--spaceM);
367
+ overflow-y: auto;
368
+ overflow-x: hidden;
369
+ }
370
+
371
+ .publicKeyDescription {
372
+ margin: 0;
373
+ font-size: var(--fontSizeBodyS);
374
+ color: var(--textBody);
375
+ }
376
+
377
+ .publicKeyMeta {
378
+ margin: 0;
379
+ font-size: var(--fontSizeBodyS);
380
+ color: var(--textTitle);
381
+ }
382
+
383
+ .publicKeyMeta span {
384
+ font-weight: var(--fontWeightMedium);
385
+ }
386
+
387
+ .publicKeyLabel {
388
+ font-size: var(--fontSizeBodyXS);
389
+ font-weight: var(--fontWeightMedium);
390
+ color: var(--textTitle);
391
+ }
392
+
393
+ .publicKeyField {
394
+ width: 100%;
395
+ max-width: 100%;
396
+ box-sizing: border-box;
397
+ min-height: 180px;
398
+ padding: var(--spaceM);
399
+ border: 1px solid color-mix(in lab, var(--text) 10%, transparent);
400
+ border-radius: var(--spaceXS);
401
+ background: color-mix(in lab, var(--background) 96%, transparent);
402
+ color: var(--textBody);
403
+ font-size: var(--fontSizeBodyXS);
404
+ line-height: 1.4;
405
+ font-family: Consolas, "Courier New", monospace;
406
+ resize: vertical;
407
+ }
408
+
409
+ .publicKeyHowToTitle {
410
+ margin: 0;
411
+ font-size: var(--fontSizeBodyS);
412
+ font-weight: var(--fontWeightMedium);
413
+ color: var(--textTitle);
414
+ }
415
+
416
+ .publicKeyHowToList {
417
+ margin: 0;
418
+ padding-left: var(--spaceL);
419
+ display: flex;
420
+ flex-direction: column;
421
+ gap: var(--spaceXS);
422
+ color: var(--textBody);
423
+ font-size: var(--fontSizeBodyS);
424
+ }
425
+
426
+ .publicKeyActions {
427
+ display: flex;
428
+ justify-content: flex-end;
429
+ gap: var(--spaceS);
430
+ }
431
+
432
+ .publicKeyStatus {
433
+ margin: 0;
434
+ font-size: var(--fontSizeBodyXS);
435
+ color: var(--textBody);
436
+ }
437
+
438
+ .publicKeyCopyButton {
439
+ background: transparent;
440
+ color: var(--primary);
441
+ border: 1px solid color-mix(in lab, var(--primary) 35%, transparent);
442
+ border-radius: var(--spaceXS);
443
+ padding: var(--spaceS) var(--spaceL);
444
+ font-size: var(--fontSizeBodyS);
445
+ font-weight: var(--fontWeightMedium);
446
+ cursor: pointer;
447
+ transition: all var(--durationS) var(--bezierFastoutSlowin);
448
+ }
449
+
450
+ .publicKeyCopyButton:hover:not(:disabled) {
451
+ background: color-mix(in lab, var(--primary) 10%, transparent);
452
+ border-color: color-mix(in lab, var(--primary) 55%, transparent);
453
+ }
454
+
455
+ .publicKeyCopyButton:disabled {
456
+ background: color-mix(in lab, var(--background) 95%, transparent);
457
+ color: var(--textLight);
458
+ border-color: color-mix(in lab, var(--text) 10%, transparent);
459
+ cursor: not-allowed;
460
+ }
461
+
462
+ .publicKeyCloseButton {
463
+ background: var(--primary);
464
+ color: white;
465
+ border: none;
466
+ border-radius: var(--spaceXS);
467
+ padding: var(--spaceS) var(--spaceL);
468
+ font-size: var(--fontSizeBodyS);
469
+ font-weight: var(--fontWeightMedium);
470
+ cursor: pointer;
471
+ transition: all var(--durationS) var(--bezierFastoutSlowin);
472
+ }
473
+
474
+ .publicKeyCloseButton:hover {
475
+ background: color-mix(in lab, var(--primary) 85%, var(--black));
476
+ }
477
+
289
478
  .divider {
290
479
  margin: var(--spaceL) 0;
291
480
  text-align: center;
@@ -296,7 +485,7 @@
296
485
  }
297
486
 
298
487
  .divider::before {
299
- content: '';
488
+ content: "";
300
489
  position: absolute;
301
490
  top: 50%;
302
491
  left: 0;
@@ -383,4 +572,4 @@
383
572
  color: var(--error);
384
573
  font-size: var(--fontSizeBodyS);
385
574
  font-weight: var(--fontWeightMedium);
386
- }
575
+ }