chordia-ui 4.0.2 → 4.0.4

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.
@@ -0,0 +1,508 @@
1
+ import React, { useState, useRef } from 'react';
2
+ import {
3
+ SquareChartGantt,
4
+ CalendarDays,
5
+ PhoneIncoming,
6
+ PhoneOutgoing,
7
+ Headset,
8
+ CircleUser,
9
+ ChevronRight,
10
+ ChevronDown,
11
+ Pencil,
12
+ Award,
13
+ Lightbulb,
14
+ Info,
15
+ } from 'lucide-react';
16
+ import { useMediaQuery } from '../../utils/useMediaQuery';
17
+
18
+ /*
19
+ * Client-specific "Interaction Quality" overview.
20
+ *
21
+ * Figma nodes:
22
+ * 1910-11142 — section header bar
23
+ * 1910-10758 — 4-column body card
24
+ *
25
+ * Purely presentational: every value comes in through props, no business logic
26
+ * or data derivation lives here (the host builds the data bag). Rendered by
27
+ * UpdatedInteractionDetails only when its `oldInteractionDetailsView` flag is on.
28
+ */
29
+
30
+ // Averta — the app's display font. Use `--default-font-averta` (not
31
+ // `--font-sans`, which can resolve to a system fallback in some scopes) so the
32
+ // text matches the rest of the interaction detail view (e.g. the transcript).
33
+ const AVERTA = 'var(--default-font-averta)';
34
+
35
+ const COLORS = {
36
+ strong: 'var(--Grey-Strong, #2E3236)',
37
+ muted: 'var(--Grey-Muted, #808183)',
38
+ absent: 'var(--Grey-absent, #D9D9D9)',
39
+ white: 'var(--Grey-White, #FFF)',
40
+ accent: 'var(--rail-orange, #C98A5A)',
41
+ track: 'var(--rail-surface-2, #E3E1D7)',
42
+ surface: '#FAF9F5',
43
+ infoIcon: 'var(--color-input-border, #ACACAD)',
44
+ };
45
+
46
+ // 5-segment score bar — mirrors the private helper in UpdatedCompassScore.jsx.
47
+ const ScoreBar = ({ value = 0, maxValue = 5 }) => {
48
+ const numericValue = Number(value);
49
+ const clampedValue = Number.isFinite(numericValue)
50
+ ? Math.min(Math.max(numericValue, 0), maxValue)
51
+ : 0;
52
+
53
+ return (
54
+ <div style={{ display: 'flex', gap: 2 }}>
55
+ {Array.from({ length: maxValue }, (_, i) => {
56
+ const segmentFill = Math.max(0, Math.min(1, clampedValue - i));
57
+ return (
58
+ <div
59
+ key={i}
60
+ style={{
61
+ width: 22,
62
+ height: 6,
63
+ borderRadius: 1,
64
+ background: COLORS.track,
65
+ overflow: 'hidden',
66
+ }}
67
+ >
68
+ {segmentFill > 0 && (
69
+ <div
70
+ style={{
71
+ width: `${segmentFill * 100}%`,
72
+ height: '100%',
73
+ background: COLORS.strong,
74
+ }}
75
+ />
76
+ )}
77
+ </div>
78
+ );
79
+ })}
80
+ </div>
81
+ );
82
+ };
83
+
84
+ // Fixed-position tooltip that escapes overflow containers — mirrors the
85
+ // InfoTooltip pattern used across the UpdatedInteractionDetails folder.
86
+ const InfoTooltip = ({ text, children }) => {
87
+ const [show, setShow] = useState(false);
88
+ const ref = useRef(null);
89
+ const [pos, setPos] = useState({ top: 0, left: 0 });
90
+
91
+ const handleEnter = () => {
92
+ if (ref.current) {
93
+ const rect = ref.current.getBoundingClientRect();
94
+ setPos({ top: rect.top - 8, left: rect.left + rect.width / 2 });
95
+ }
96
+ setShow(true);
97
+ };
98
+
99
+ return (
100
+ <div
101
+ ref={ref}
102
+ style={{ position: 'relative', display: 'inline-flex' }}
103
+ onMouseEnter={handleEnter}
104
+ onMouseLeave={() => setShow(false)}
105
+ >
106
+ {children}
107
+ {show && text && (
108
+ <div style={{
109
+ position: 'fixed',
110
+ top: pos.top,
111
+ left: pos.left,
112
+ transform: 'translate(-50%, -100%)',
113
+ display: 'flex',
114
+ width: 'max-content',
115
+ maxWidth: 300,
116
+ padding: 10,
117
+ alignItems: 'center',
118
+ borderRadius: 4,
119
+ border: `1px solid ${COLORS.absent}`,
120
+ background: COLORS.strong,
121
+ zIndex: 9999,
122
+ pointerEvents: 'none',
123
+ }}>
124
+ <span style={{ color: '#FFF', fontSize: 12, fontWeight: 400, lineHeight: '140%' }}>
125
+ {text}
126
+ </span>
127
+ </div>
128
+ )}
129
+ </div>
130
+ );
131
+ };
132
+
133
+ // Normalise a strengths/improvements entry that may be a plain string or a
134
+ // { text, quote } object — same handling as UpdatedCoachingSynthesisCard.jsx.
135
+ const itemText = (item) => (typeof item === 'string' ? item : item?.text || '');
136
+
137
+ // "What was great?" / "What can be better?" card.
138
+ const FeedbackCard = ({ icon: Icon, title, items = [], loading = false, emptyText }) => (
139
+ <div style={{
140
+ display: 'flex',
141
+ flexDirection: 'column',
142
+ gap: 8,
143
+ padding: 16,
144
+ borderRadius: 8,
145
+ background: COLORS.surface,
146
+ flex: 1,
147
+ minWidth: 0,
148
+ alignSelf: 'stretch',
149
+ }}>
150
+ <div style={{ display: 'flex', gap: 4, alignItems: 'center' }}>
151
+ <Icon size={16} color={COLORS.strong} strokeWidth={1.75} />
152
+ <span style={{
153
+ flex: 1,
154
+ fontSize: 14,
155
+ fontWeight: 600,
156
+ color: COLORS.strong,
157
+ fontFamily: AVERTA,
158
+ lineHeight: '20px',
159
+ }}>
160
+ {title}
161
+ </span>
162
+ </div>
163
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
164
+ {loading ? (
165
+ <span style={{ fontSize: 14, color: COLORS.muted, fontFamily: AVERTA, lineHeight: '20px' }}>
166
+ Loading...
167
+ </span>
168
+ ) : items.length > 0 ? (
169
+ items.map((item, i) => {
170
+ const text = itemText(item);
171
+ if (!text) return null;
172
+ return (
173
+ <p key={i} style={{
174
+ margin: 0,
175
+ fontSize: 14,
176
+ fontWeight: 400,
177
+ color: COLORS.strong,
178
+ fontFamily: AVERTA,
179
+ lineHeight: '20px',
180
+ wordBreak: 'break-word',
181
+ }}>
182
+ {text}
183
+ </p>
184
+ );
185
+ })
186
+ ) : (
187
+ <span style={{ fontSize: 14, color: COLORS.muted, fontFamily: AVERTA, lineHeight: '20px' }}>
188
+ {emptyText}
189
+ </span>
190
+ )}
191
+ </div>
192
+ </div>
193
+ );
194
+
195
+ const InteractionQualityOverview = ({
196
+ // Header (Figma 1910-11142)
197
+ title = 'Interaction Quality',
198
+ dateStr = '',
199
+ direction = 'Inbound',
200
+ agentName = 'Agent',
201
+ agentFullName,
202
+ customerName = 'Customer',
203
+ customerFullName,
204
+ sessionCount = 0,
205
+ onViewAllSessions,
206
+ canEdit = false,
207
+ onEditInteraction,
208
+ moreDetailsContent = null,
209
+ // Body card (Figma 1910-10758)
210
+ scorePercent = null,
211
+ scoreOwnerName = '',
212
+ details = [],
213
+ summary = '',
214
+ csat = { value: 0, maxValue: 5 },
215
+ strengths = [],
216
+ improvements = [],
217
+ loading = false,
218
+ }) => {
219
+ const isPhone = useMediaQuery('(max-width: 640px)');
220
+ const [showMoreDetails, setShowMoreDetails] = useState(false);
221
+
222
+ const DirectionIcon = String(direction).toLowerCase() === 'outbound' ? PhoneOutgoing : PhoneIncoming;
223
+ const scoreLabel = scoreOwnerName
224
+ ? `${scoreOwnerName}'s Compass Score`
225
+ : 'Compass Score';
226
+
227
+ return (
228
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 24, alignSelf: 'stretch' }}>
229
+ {/* ── Header bar (Figma 1910-11142) ── */}
230
+ <div style={{
231
+ display: 'flex',
232
+ flexWrap: isPhone ? 'wrap' : 'nowrap',
233
+ alignItems: isPhone ? 'flex-start' : 'center',
234
+ justifyContent: 'space-between',
235
+ gap: isPhone ? '8px 16px' : 16,
236
+ paddingBottom: 12,
237
+ borderBottom: `1px solid ${COLORS.absent}`,
238
+ alignSelf: 'stretch',
239
+ }}>
240
+ {/* Title */}
241
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 }}>
242
+ <SquareChartGantt size={24} color={COLORS.accent} strokeWidth={2} />
243
+ <span style={{
244
+ fontSize: 16,
245
+ fontWeight: 600,
246
+ color: COLORS.strong,
247
+ fontFamily: AVERTA,
248
+ lineHeight: 1.2,
249
+ textTransform: 'uppercase',
250
+ whiteSpace: 'nowrap',
251
+ }}>
252
+ {title}
253
+ </span>
254
+ </div>
255
+
256
+ {/* Metadata */}
257
+ <div style={{
258
+ display: 'flex',
259
+ flexWrap: isPhone ? 'wrap' : 'nowrap',
260
+ alignItems: 'center',
261
+ gap: isPhone ? '8px 12px' : 16,
262
+ ...(isPhone ? { width: '100%' } : {}),
263
+ }}>
264
+ {/* Date */}
265
+ {dateStr && (
266
+ <div style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '8px 0' }}>
267
+ <CalendarDays size={16} color={COLORS.muted} strokeWidth={1.5} />
268
+ <span style={{ fontSize: 13, fontWeight: 400, color: COLORS.strong, fontFamily: AVERTA, lineHeight: 1.2, whiteSpace: 'nowrap' }}>
269
+ {dateStr}
270
+ </span>
271
+ </div>
272
+ )}
273
+
274
+ {/* Direction */}
275
+ <div style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '8px 0' }}>
276
+ <DirectionIcon size={16} color={COLORS.muted} strokeWidth={1.5} />
277
+ <span style={{ fontSize: 13, fontWeight: 400, color: COLORS.strong, fontFamily: AVERTA, lineHeight: 1.2, whiteSpace: 'nowrap' }}>
278
+ {direction}
279
+ </span>
280
+ </div>
281
+
282
+ {/* Agent */}
283
+ <div style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '8px 0' }} title={agentFullName || agentName}>
284
+ <Headset size={16} color={COLORS.muted} strokeWidth={1.5} />
285
+ <span style={{ fontSize: 13, fontWeight: 400, color: COLORS.strong, fontFamily: AVERTA, lineHeight: 1.2, whiteSpace: 'nowrap' }}>
286
+ {agentName}
287
+ </span>
288
+ </div>
289
+
290
+ {/* More Details toggle — reveals the reused context grid below */}
291
+ {moreDetailsContent && (
292
+ <button
293
+ type="button"
294
+ onClick={() => setShowMoreDetails((v) => !v)}
295
+ style={{
296
+ display: 'flex',
297
+ alignItems: 'center',
298
+ gap: 6,
299
+ padding: '8px 0',
300
+ background: 'none',
301
+ border: 'none',
302
+ cursor: 'pointer',
303
+ }}
304
+ >
305
+ <span style={{ fontSize: 13, fontWeight: 500, color: COLORS.strong, fontFamily: AVERTA, lineHeight: 1.2, whiteSpace: 'nowrap' }}>
306
+ More Details
307
+ </span>
308
+ <ChevronDown
309
+ size={16}
310
+ color={COLORS.strong}
311
+ strokeWidth={1.75}
312
+ style={{ transform: showMoreDetails ? 'rotate(180deg)' : 'none', transition: 'transform 0.15s' }}
313
+ />
314
+ </button>
315
+ )}
316
+
317
+ {/* Edit */}
318
+ {canEdit && onEditInteraction && (
319
+ <button
320
+ type="button"
321
+ onClick={onEditInteraction}
322
+ style={{
323
+ display: 'flex',
324
+ alignItems: 'center',
325
+ justifyContent: 'center',
326
+ gap: 8,
327
+ height: 28,
328
+ padding: '8px 10px',
329
+ borderRadius: 10,
330
+ border: '1px solid var(--Neutral-250, #BFBFBF)',
331
+ background: 'none',
332
+ cursor: 'pointer',
333
+ }}
334
+ >
335
+ <Pencil size={16} color="var(--Neutral-800, #323232)" strokeWidth={1.75} />
336
+ <span style={{ fontSize: 14, fontWeight: 400, color: 'var(--Neutral-800, #323232)', fontFamily: AVERTA, lineHeight: '24px', whiteSpace: 'nowrap' }}>
337
+ Edit
338
+ </span>
339
+ </button>
340
+ )}
341
+
342
+ {/* Customer sessions pill */}
343
+ {sessionCount > 1 ? (
344
+ <button
345
+ type="button"
346
+ onClick={onViewAllSessions}
347
+ title={customerFullName || customerName}
348
+ style={{
349
+ display: 'flex',
350
+ alignItems: 'center',
351
+ gap: 16,
352
+ padding: '0 16px',
353
+ height: 34,
354
+ borderRadius: 8,
355
+ background: COLORS.track,
356
+ border: 'none',
357
+ cursor: onViewAllSessions ? 'pointer' : 'default',
358
+ }}
359
+ >
360
+ <div style={{ display: 'flex', alignItems: 'center', gap: 4, minWidth: 0 }}>
361
+ <CircleUser size={16} color={COLORS.strong} strokeWidth={1.5} />
362
+ <span style={{ fontSize: 13, fontWeight: 400, color: COLORS.strong, lineHeight: 1.2, whiteSpace: 'nowrap' }}>
363
+ <span style={{ fontWeight: 600 }}>{customerName}</span>{' '}
364
+ <span>({sessionCount} Sessions)</span>
365
+ </span>
366
+ </div>
367
+ <ChevronRight size={16} color={COLORS.strong} strokeWidth={1.7} />
368
+ </button>
369
+ ) : (
370
+ <div style={{ display: 'flex', alignItems: 'center', gap: 4, padding: '8px 0' }} title={customerFullName || customerName}>
371
+ <CircleUser size={16} color={COLORS.muted} strokeWidth={1.5} />
372
+ <span style={{ fontSize: 13, fontWeight: 400, color: COLORS.strong, lineHeight: 1.2, whiteSpace: 'nowrap' }}>
373
+ {customerName}
374
+ </span>
375
+ </div>
376
+ )}
377
+ </div>
378
+ </div>
379
+
380
+ {/* More Details expandable — reuses the existing context grid element */}
381
+ {moreDetailsContent && showMoreDetails && (
382
+ <div style={{ alignSelf: 'stretch' }}>
383
+ {moreDetailsContent}
384
+ </div>
385
+ )}
386
+
387
+ {/* ── Body: 4-column card (Figma 1910-10758) ── */}
388
+ <div style={{
389
+ display: 'flex',
390
+ flexDirection: isPhone ? 'column' : 'row',
391
+ alignItems: 'stretch',
392
+ gap: 24,
393
+ alignSelf: 'stretch',
394
+ }}>
395
+ {/* Col 1 — score + details. Top-aligned so the score lines up with the
396
+ "Summary" title and the feedback-card headers (Figma 1910-10759). */}
397
+ <div style={{
398
+ display: 'flex',
399
+ flexDirection: 'column',
400
+ gap: 24,
401
+ flex: 1,
402
+ minWidth: 0,
403
+ justifyContent: 'flex-start',
404
+ }}>
405
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8, color: COLORS.strong }}>
406
+ <span style={{
407
+ fontSize: 80,
408
+ fontWeight: 400,
409
+ fontFamily: AVERTA,
410
+ lineHeight: 1,
411
+ whiteSpace: 'nowrap',
412
+ }}>
413
+ {scorePercent != null ? (
414
+ <>
415
+ {scorePercent}
416
+ {/* Percent sign as a refined unit suffix: about half the
417
+ digit size, same colour as the number, on its baseline. */}
418
+ <span style={{
419
+ fontSize: 36,
420
+ fontWeight: 400,
421
+ marginLeft: 4,
422
+ }}>
423
+ %
424
+ </span>
425
+ </>
426
+ ) : '—'}
427
+ </span>
428
+ <span style={{ fontSize: 14, fontWeight: 600, fontFamily: AVERTA, lineHeight: '20px' }}>
429
+ {scoreLabel}
430
+ </span>
431
+ </div>
432
+
433
+ {details.length > 0 && (
434
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 12, fontSize: 13, lineHeight: 1.2 }}>
435
+ {details.map((row, i) => (
436
+ <div key={i} style={{
437
+ display: 'flex',
438
+ flexWrap: 'wrap',
439
+ alignItems: 'center',
440
+ justifyContent: 'space-between',
441
+ gap: '4px 24px',
442
+ paddingTop: 8,
443
+ paddingBottom: 12,
444
+ borderBottom: `1px solid ${COLORS.absent}`,
445
+ }}>
446
+ <span style={{ color: COLORS.strong, fontFamily: AVERTA }}>{row.label}</span>
447
+ <span style={{ color: COLORS.muted, fontFamily: AVERTA }}>{row.value}</span>
448
+ </div>
449
+ ))}
450
+ </div>
451
+ )}
452
+ </div>
453
+
454
+ {/* Col 2 — summary + CSAT. Matches the feedback cards' 16px top inset so
455
+ the "Summary" title lines up with the card titles (Figma 1910-10759). */}
456
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8, flex: 1, minWidth: 0, paddingTop: 16 }}>
457
+ <span style={{ fontSize: 14, fontWeight: 600, color: COLORS.strong, fontFamily: AVERTA, lineHeight: '20px' }}>
458
+ Summary
459
+ </span>
460
+ <p style={{
461
+ margin: 0,
462
+ fontSize: 14,
463
+ fontWeight: 400,
464
+ color: COLORS.strong,
465
+ fontFamily: AVERTA,
466
+ lineHeight: '20px',
467
+ wordBreak: 'break-word',
468
+ }}>
469
+ {loading && !summary ? 'Loading…' : summary || '—'}
470
+ </p>
471
+ {csat && (
472
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8, padding: '8px 0' }}>
473
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
474
+ <span style={{ fontSize: 12, fontWeight: 400, color: COLORS.strong, fontFamily: AVERTA, lineHeight: 1.2 }}>
475
+ CSAT Score
476
+ </span>
477
+ <InfoTooltip text="Predicted Customer Satisfaction">
478
+ <Info size={14} color={COLORS.infoIcon} strokeWidth={1} style={{ cursor: 'pointer' }} />
479
+ </InfoTooltip>
480
+ </div>
481
+ <ScoreBar value={csat.value} maxValue={csat.maxValue || 5} />
482
+ </div>
483
+ )}
484
+ </div>
485
+
486
+ {/* Col 3 — What was great? */}
487
+ <FeedbackCard
488
+ icon={Award}
489
+ title="What was great?"
490
+ items={strengths}
491
+ loading={loading}
492
+ emptyText="No highlights available."
493
+ />
494
+
495
+ {/* Col 4 — What can be better? */}
496
+ <FeedbackCard
497
+ icon={Lightbulb}
498
+ title="What can be better?"
499
+ items={improvements}
500
+ loading={loading}
501
+ emptyText="No suggestions available."
502
+ />
503
+ </div>
504
+ </div>
505
+ );
506
+ };
507
+
508
+ export default InteractionQualityOverview;
@@ -323,43 +323,50 @@ const UpdatedCompassScore = ({
323
323
  alignItems: 'center',
324
324
  display: 'flex',
325
325
  }}>
326
- <GaugeMeter score={score} minScore={minScore} maxScore={maxScore} />
326
+ {/* Gauge with the compass pin overlaid. The pin is absolutely
327
+ positioned (out of layout flow) so it doesn't push the score down
328
+ — the number then sits directly below the gauge (Figma 1898-18916,
329
+ "Label + Value" at top:128 just under the 120px gauge). */}
330
+ <div style={{ position: 'relative', width: '100%', display: 'flex', justifyContent: 'center' }}>
331
+ <GaugeMeter score={score} minScore={minScore} maxScore={maxScore} />
327
332
 
328
- {/* Compass pin — centered below gauge, needle rotates to point at score */}
329
- {(() => {
330
- // Pin sits at gauge center; needle rotates from 180° (score=0) to 0° (score=max)
331
- const pct = maxScore > minScore ? (clampedScore - minScore) / (maxScore - minScore) : 0;
332
- // SVG pin's default needle points to top-right (~45°)
333
- // We need: score=0 → needle points left (180°), score=max needle points right (0°)
334
- // Target angle on gauge: 180 - pct*180
335
- // Pin default orientation: needle at ~45° from top offset is 45
336
- // Rotation needed: -(targetAngle - 45) to align needle with gauge position
337
- const targetAngle = 180 - pct * 180;
338
- const rotateDeg = -(targetAngle - 45);
339
- return (
340
- <svg
341
- width="34" height="35" viewBox="0 0 34 35" fill="none"
342
- xmlns="http://www.w3.org/2000/svg"
343
- style={{
344
- marginTop: -24,
345
- transform: `rotate(${rotateDeg}deg)`,
346
- transformOrigin: 'center center',
347
- }}
348
- >
349
- <path d="M16 25.2169C19.958 25.2169 23.1667 22.0083 23.1667 18.0503C23.1667 14.0922 19.958 10.8836 16 10.8836C12.042 10.8836 8.83334 14.0922 8.83334 18.0503C8.83334 22.0083 12.042 25.2169 16 25.2169Z" fill="var(--rail-orange, #C98A5A)" />
350
- <path fillRule="evenodd" clipRule="evenodd" d="M30.4791 11.2328L33.4351 0L21.6888 3.09113C19.9212 2.41855 18.0036 2.05025 16 2.05025C7.16344 2.05025 0 9.2137 0 18.0503C0 26.8868 7.16344 34.0503 16 34.0503C24.8366 34.0503 32 26.8868 32 18.0503C32 15.6119 31.4546 13.301 30.4791 11.2328ZM16 30.5503C22.9036 30.5503 28.5 24.9538 28.5 18.0503C28.5 11.1467 22.9036 5.55025 16 5.55025C9.09644 5.55025 3.5 11.1467 3.5 18.0503C3.5 24.9538 9.09644 30.5503 16 30.5503Z" fill="var(--rail-orange, #C98A5A)" />
351
- </svg>
352
- );
353
- })()}
333
+ {/* Compass pin — needle rotates to point at the score. Centered in
334
+ the lower part of the gauge opening; `top` is a percentage so it
335
+ tracks the gauge as it scales down responsively. */}
336
+ {(() => {
337
+ // score=0 needle points left (180°); score=max → points right (0°).
338
+ // Pin's default needle points top-right (~45°), so rotate by
339
+ // -(targetAngle - 45) to align it with the gauge position.
340
+ const pct = maxScore > minScore ? (clampedScore - minScore) / (maxScore - minScore) : 0;
341
+ const targetAngle = 180 - pct * 180;
342
+ const rotateDeg = -(targetAngle - 45);
343
+ return (
344
+ <svg
345
+ width="34" height="35" viewBox="0 0 34 35" fill="none"
346
+ xmlns="http://www.w3.org/2000/svg"
347
+ style={{
348
+ position: 'absolute',
349
+ left: '50%',
350
+ top: '82%',
351
+ transform: `translate(-50%, -50%) rotate(${rotateDeg}deg)`,
352
+ transformOrigin: 'center center',
353
+ }}
354
+ >
355
+ <path d="M16 25.2169C19.958 25.2169 23.1667 22.0083 23.1667 18.0503C23.1667 14.0922 19.958 10.8836 16 10.8836C12.042 10.8836 8.83334 14.0922 8.83334 18.0503C8.83334 22.0083 12.042 25.2169 16 25.2169Z" fill="var(--rail-orange, #C98A5A)" />
356
+ <path fillRule="evenodd" clipRule="evenodd" d="M30.4791 11.2328L33.4351 0L21.6888 3.09113C19.9212 2.41855 18.0036 2.05025 16 2.05025C7.16344 2.05025 0 9.2137 0 18.0503C0 26.8868 7.16344 34.0503 16 34.0503C24.8366 34.0503 32 26.8868 32 18.0503C32 15.6119 31.4546 13.301 30.4791 11.2328ZM16 30.5503C22.9036 30.5503 28.5 24.9538 28.5 18.0503C28.5 11.1467 22.9036 5.55025 16 5.55025C9.09644 5.55025 3.5 11.1467 3.5 18.0503C3.5 24.9538 9.09644 30.5503 16 30.5503Z" fill="var(--rail-orange, #C98A5A)" />
357
+ </svg>
358
+ );
359
+ })()}
360
+ </div>
354
361
 
355
- {/* Score number below gauge — with the outcome value in round
356
- brackets next to it (Figma 1798-5168), e.g. "4 (80%)". */}
362
+ {/* Score number just below the gauge — outcome value in round
363
+ brackets next to it (Figma 1898-18916), e.g. "4 (80%)". */}
357
364
  <div style={{
358
365
  fontSize: 28,
359
366
  fontWeight: 400,
360
367
  color: COLORS.strong,
361
368
  fontFamily: 'var(--font-sans)',
362
- lineHeight: 1,
369
+ lineHeight: '28px',
363
370
  textAlign: 'center',
364
371
  marginTop: 8,
365
372
  display: 'flex',
@@ -369,19 +376,19 @@ const UpdatedCompassScore = ({
369
376
  }}>
370
377
  <span>{scoreText}</span>
371
378
  {outcomePercent != null && (
372
- <span style={{ fontSize: 16, color: COLORS.muted, lineHeight: 1 }}>
379
+ <span style={{ fontSize: 14, color: COLORS.muted, lineHeight: '28px' }}>
373
380
  ({outcomePercent}%)
374
381
  </span>
375
382
  )}
376
383
  </div>
377
384
  <div style={{
378
- fontSize: 15,
379
- fontWeight: 500,
385
+ fontSize: 14,
386
+ fontWeight: 400,
380
387
  color: COLORS.strong,
381
388
  fontFamily: 'var(--font-sans)',
382
- lineHeight: 1.2,
389
+ lineHeight: '20px',
383
390
  textAlign: 'center',
384
- marginTop: 8,
391
+ marginTop: 2,
385
392
  }}>
386
393
  Compass Score
387
394
  </div>