kyd-shared-badge 0.2.28 → 0.2.29
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.
package/package.json
CHANGED
|
@@ -20,6 +20,7 @@ import Skills from './components/Skills';
|
|
|
20
20
|
import CategoryBars from './components/CategoryBars';
|
|
21
21
|
import SkillsAppendixTable from './components/SkillsAppendixTable';
|
|
22
22
|
import { BusinessRulesProvider } from './components/BusinessRulesContext';
|
|
23
|
+
import AnimateOnMount from './components/AnimateOnMount';
|
|
23
24
|
|
|
24
25
|
// const hexToRgba = (hex: string, alpha: number) => {
|
|
25
26
|
// const clean = hex.replace('#', '');
|
|
@@ -138,20 +139,23 @@ const SharedBadgeDisplay = ({ badgeData }: { badgeData: PublicBadgeData }) => {
|
|
|
138
139
|
<BusinessRulesProvider items={graphInsights?.business_rules_all}>
|
|
139
140
|
<div className={`${wrapperMaxWidth} mx-auto`}>
|
|
140
141
|
{/* Share controls removed; app-level pages render their own actions */}
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
142
|
+
<AnimateOnMount delayMs={40} offsetY={8}>
|
|
143
|
+
<ReportHeader
|
|
144
|
+
badgeId={badgeId}
|
|
145
|
+
developerName={badgeData.developerName}
|
|
146
|
+
updatedAt={updatedAt}
|
|
147
|
+
score={overallFinalPercent || 0}
|
|
148
|
+
isPublic={true}
|
|
149
|
+
badgeImageUrl={badgeData.badgeImageUrl || ''}
|
|
150
|
+
summary={report_summary}
|
|
151
|
+
countries={(assessmentResult?.screening_sources?.ip_risk_analysis?.raw_data?.countries) || []}
|
|
152
|
+
/>
|
|
153
|
+
</AnimateOnMount>
|
|
154
|
+
<AnimateOnMount delayMs={80} offsetY={10}>
|
|
155
|
+
<div
|
|
156
|
+
className={'rounded-xl shadow-xl p-6 sm:p-8 mt-8 border'}
|
|
157
|
+
style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)' }}
|
|
158
|
+
>
|
|
155
159
|
|
|
156
160
|
<div className={'space-y-12 divide-y'} style={{ borderColor: 'var(--icon-button-secondary)' }}>
|
|
157
161
|
<div className="pt-8 first:pt-0">
|
|
@@ -164,15 +168,17 @@ const SharedBadgeDisplay = ({ badgeData }: { badgeData: PublicBadgeData }) => {
|
|
|
164
168
|
const label = ui?.label || 'EVIDENCE';
|
|
165
169
|
const top = ui?.top_movers && ui.top_movers.length > 0 ? ui.top_movers : topBusinessForGenre('Technical');
|
|
166
170
|
return (
|
|
167
|
-
<
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
171
|
+
<AnimateOnMount delayMs={120} offsetY={8}>
|
|
172
|
+
<GaugeCard
|
|
173
|
+
key={'technical-card'}
|
|
174
|
+
title={'KYD Technical'}
|
|
175
|
+
description={'The gauge visualization shows a weighted composite of technical evidence, with rightward movement indicating stronger indications of developer capability'}
|
|
176
|
+
percent={pct}
|
|
177
|
+
label={label}
|
|
178
|
+
topMovers={top?.map(t => ({ label: t?.label, uid: t?.uid }))}
|
|
179
|
+
topMoversTitle={'Top Score Movers'}
|
|
180
|
+
/>
|
|
181
|
+
</AnimateOnMount>
|
|
176
182
|
);
|
|
177
183
|
})()}
|
|
178
184
|
|
|
@@ -184,15 +190,17 @@ const SharedBadgeDisplay = ({ badgeData }: { badgeData: PublicBadgeData }) => {
|
|
|
184
190
|
const top = ui?.top_movers && ui.top_movers.length > 0 ? ui.top_movers : topBusinessForGenre('Risk');
|
|
185
191
|
const tooltip = 'Higher bar filled indicates lower overall risk; movement to the right reflects improved risk posture.';
|
|
186
192
|
return (
|
|
187
|
-
<
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
193
|
+
<AnimateOnMount delayMs={160} offsetY={8}>
|
|
194
|
+
<RiskCard
|
|
195
|
+
title={'KYD Risk'}
|
|
196
|
+
description={'The bar chart visualizes relative risk levels, where shorter bars denote lower risk and taller bars indicate greater exposure.'}
|
|
197
|
+
percentGood={pctGood}
|
|
198
|
+
label={label}
|
|
199
|
+
topMovers={top?.map(t => ({ label: t?.label, uid: t?.uid }))}
|
|
200
|
+
topMoversTitle={'Top Score Movers'}
|
|
201
|
+
tooltipText={tooltip}
|
|
202
|
+
/>
|
|
203
|
+
</AnimateOnMount>
|
|
196
204
|
);
|
|
197
205
|
})()}
|
|
198
206
|
|
|
@@ -202,16 +210,18 @@ const SharedBadgeDisplay = ({ badgeData }: { badgeData: PublicBadgeData }) => {
|
|
|
202
210
|
const label = 'AI Transparency'// TODO: calculate label frontend
|
|
203
211
|
const topMovers = ai_usage_summary?.key_findings || []
|
|
204
212
|
return (
|
|
205
|
-
<
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
213
|
+
<AnimateOnMount delayMs={200} offsetY={8}>
|
|
214
|
+
<GaugeCard
|
|
215
|
+
key={'ai-card'}
|
|
216
|
+
title={'KYD AI'}
|
|
217
|
+
description={'Indicates the degree to which AI-assisted code is explicitly disclosed across analyzed files.'}
|
|
218
|
+
percent={ai_usage_summary?.transparency_score}
|
|
219
|
+
label={label}
|
|
220
|
+
// id non-functional
|
|
221
|
+
topMovers={topMovers.map(t => ({ label: t, uid: 'ai-usage' }))}
|
|
222
|
+
topMoversTitle={'Key Findings'}
|
|
223
|
+
/>
|
|
224
|
+
</AnimateOnMount>
|
|
215
225
|
);
|
|
216
226
|
})()}
|
|
217
227
|
</div>
|
|
@@ -222,16 +232,19 @@ const SharedBadgeDisplay = ({ badgeData }: { badgeData: PublicBadgeData }) => {
|
|
|
222
232
|
<div key={'Technical'} className='pt-8 space-y-8' style={{ borderColor: 'var(--icon-button-secondary)'}}>
|
|
223
233
|
<h4 className={'text-2xl font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>KYD Technical</h4>
|
|
224
234
|
{/* technical graph insights */}
|
|
225
|
-
<
|
|
226
|
-
<
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
235
|
+
<AnimateOnMount delayMs={120} offsetY={8}>
|
|
236
|
+
<div className="">
|
|
237
|
+
<GraphInsights
|
|
238
|
+
graphInsights={graphInsights}
|
|
239
|
+
categories={genreMapping?.['Technical'] as string[]}
|
|
240
|
+
genre={'Technical'}
|
|
241
|
+
scoringSummary={scoringSummary}
|
|
242
|
+
/>
|
|
243
|
+
</div>
|
|
244
|
+
</AnimateOnMount>
|
|
233
245
|
|
|
234
246
|
{/* category bars and contributing factors */}
|
|
247
|
+
<AnimateOnMount delayMs={160} offsetY={10}>
|
|
235
248
|
<div className="grid grid-cols-1 lg:grid-cols-12 w-full gap-8 items-stretch py-8 border-t" style={{ borderColor: 'var(--icon-button-secondary)'}}>
|
|
236
249
|
|
|
237
250
|
{/* Left: Bars */}
|
|
@@ -278,31 +291,38 @@ const SharedBadgeDisplay = ({ badgeData }: { badgeData: PublicBadgeData }) => {
|
|
|
278
291
|
</div>
|
|
279
292
|
</div>
|
|
280
293
|
</div>
|
|
294
|
+
</AnimateOnMount>
|
|
281
295
|
|
|
282
|
-
<
|
|
283
|
-
<
|
|
284
|
-
|
|
285
|
-
<
|
|
296
|
+
<AnimateOnMount delayMs={180} offsetY={10}>
|
|
297
|
+
<div className="pt-8 border-t" style={{ borderColor: 'var(--icon-button-secondary)'}}>
|
|
298
|
+
<h3 className={'text-xl font-bold mb-3'} style={{ color: 'var(--text-main)' }}>KYD Technical - Skills Insights</h3>
|
|
299
|
+
<div className={'prose prose-sm max-w-none mb-6 space-y-4'} style={{ color: 'var(--text-secondary)' }}>
|
|
300
|
+
<Skills skillsMatrix={skillsMatrix} skillsCategoryRadar={graphInsights?.skillsCategoryRadar} />
|
|
301
|
+
</div>
|
|
286
302
|
</div>
|
|
287
|
-
</
|
|
303
|
+
</AnimateOnMount>
|
|
288
304
|
|
|
289
305
|
</div>
|
|
290
306
|
</div>
|
|
291
307
|
|
|
292
308
|
|
|
293
309
|
|
|
310
|
+
<AnimateOnMount delayMs={200} offsetY={10}>
|
|
294
311
|
<div className="pt-8 space-y-8">
|
|
295
312
|
<h3 className={'text-2xl font-bold'} style={{ color: 'var(--text-main)' }}>KYD Risk - Overview</h3>
|
|
296
313
|
|
|
297
314
|
{/* Risk Graph Insights and Category Bars */}
|
|
298
|
-
<
|
|
299
|
-
<
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
315
|
+
<AnimateOnMount delayMs={220} offsetY={8}>
|
|
316
|
+
<div className="">
|
|
317
|
+
<GraphInsights
|
|
318
|
+
graphInsights={graphInsights}
|
|
319
|
+
categories={genreMapping?.['Risk'] as string[]}
|
|
320
|
+
genre={'Risk'}
|
|
321
|
+
scoringSummary={scoringSummary}
|
|
322
|
+
/>
|
|
323
|
+
</div>
|
|
324
|
+
</AnimateOnMount>
|
|
325
|
+
<AnimateOnMount delayMs={240} offsetY={10}>
|
|
306
326
|
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 w-full items-stretch py-8 border-y" style={{ borderColor: 'var(--icon-button-secondary)' }}>
|
|
307
327
|
{/* Left: Bars */}
|
|
308
328
|
<div className="lg:col-span-8 h-full">
|
|
@@ -347,6 +367,7 @@ const SharedBadgeDisplay = ({ badgeData }: { badgeData: PublicBadgeData }) => {
|
|
|
347
367
|
</div>
|
|
348
368
|
</div>
|
|
349
369
|
</div>
|
|
370
|
+
</AnimateOnMount>
|
|
350
371
|
|
|
351
372
|
{/* cyber risk display */}
|
|
352
373
|
{badgeData.optOutScreening ? (
|
|
@@ -372,6 +393,7 @@ const SharedBadgeDisplay = ({ badgeData }: { badgeData: PublicBadgeData }) => {
|
|
|
372
393
|
const fbiMatches = ss?.fbi_matches && (ss.fbi_matches.length > 0);
|
|
373
394
|
if (!(ofacMatches || cslDetails || fbiMatches)) return null;
|
|
374
395
|
return (
|
|
396
|
+
<AnimateOnMount delayMs={260} offsetY={8}>
|
|
375
397
|
<div className={'mb-8 rounded-lg border p-4'} style={{ borderColor: 'var(--icon-button-secondary)', backgroundColor: 'var(--content-card-background)' }}>
|
|
376
398
|
<h4 className={'text-lg font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>3A. Sanctions Matches</h4>
|
|
377
399
|
{/* OFAC matches */}
|
|
@@ -433,47 +455,59 @@ const SharedBadgeDisplay = ({ badgeData }: { badgeData: PublicBadgeData }) => {
|
|
|
433
455
|
</div>
|
|
434
456
|
)}
|
|
435
457
|
</div>
|
|
458
|
+
</AnimateOnMount>
|
|
436
459
|
);
|
|
437
460
|
})()}
|
|
438
|
-
<
|
|
461
|
+
<AnimateOnMount delayMs={280} offsetY={8}>
|
|
462
|
+
<IpRiskAnalysisDisplay ipRiskAnalysis={screening_sources?.ip_risk_analysis} />
|
|
463
|
+
</AnimateOnMount>
|
|
439
464
|
</>
|
|
440
465
|
)}
|
|
441
466
|
|
|
442
467
|
</div>
|
|
468
|
+
</AnimateOnMount>
|
|
443
469
|
|
|
444
470
|
{/* Connected Platforms */}
|
|
445
|
-
<
|
|
471
|
+
<AnimateOnMount delayMs={260} offsetY={10}>
|
|
472
|
+
<ConnectedPlatforms accounts={connected} />
|
|
473
|
+
</AnimateOnMount>
|
|
446
474
|
|
|
447
475
|
|
|
476
|
+
<AnimateOnMount delayMs={280} offsetY={10}>
|
|
448
477
|
<div className="pt-8">
|
|
449
478
|
<h3 className={'text-2xl font-bold mb-4'} style={{ color: 'var(--text-main)' }}>Appendix: Data Sources</h3>
|
|
450
479
|
<div className="space-y-8">
|
|
451
480
|
|
|
452
481
|
{/* Skills */}
|
|
453
|
-
<
|
|
454
|
-
<
|
|
455
|
-
|
|
456
|
-
|
|
482
|
+
<AnimateOnMount delayMs={300} offsetY={8}>
|
|
483
|
+
<div>
|
|
484
|
+
<h4 id="appendix-skills" className={'text-lg font-bold mb-4'} style={{ color: 'var(--text-main)' }}>Skills</h4>
|
|
485
|
+
<SkillsAppendixTable skillsAll={skillsAll} />
|
|
486
|
+
</div>
|
|
487
|
+
</AnimateOnMount>
|
|
457
488
|
|
|
458
489
|
{/* Observations */}
|
|
459
490
|
{Array.isArray(graphInsights?.business_rules_all) && graphInsights.business_rules_all.length > 0 && (
|
|
460
|
-
<
|
|
461
|
-
<
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
491
|
+
<AnimateOnMount delayMs={320} offsetY={8}>
|
|
492
|
+
<div>
|
|
493
|
+
<h4 className={'text-lg font-bold mb-4'} style={{ color: 'var(--text-main)' }}>Observations</h4>
|
|
494
|
+
<AppendixTables
|
|
495
|
+
type="business_rules"
|
|
496
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
497
|
+
sources={graphInsights.business_rules_all as any}
|
|
498
|
+
searchedAt={updatedAt}
|
|
499
|
+
developerName={developerName || 'this developer'}
|
|
500
|
+
/>
|
|
501
|
+
</div>
|
|
502
|
+
</AnimateOnMount>
|
|
470
503
|
)}
|
|
471
504
|
|
|
472
505
|
{/* Sanctions & Watchlists */}
|
|
473
506
|
{!badgeData.optOutScreening && screening_sources && (
|
|
474
|
-
<
|
|
475
|
-
<
|
|
476
|
-
|
|
507
|
+
<AnimateOnMount delayMs={340} offsetY={8}>
|
|
508
|
+
<div>
|
|
509
|
+
<h4 className={'text-lg font-bold mb-4'} style={{ color: 'var(--text-main)' }}>Sanctions & Watchlists</h4>
|
|
510
|
+
{(() => {
|
|
477
511
|
const ofacProvided = screening_sources.ofac_screen?.sources || [];
|
|
478
512
|
const lists = [...(screening_sources.ofac_lists || []), ...ofacProvided];
|
|
479
513
|
const seen: { [k: string]: boolean } = {};
|
|
@@ -494,23 +528,28 @@ const SharedBadgeDisplay = ({ badgeData }: { badgeData: PublicBadgeData }) => {
|
|
|
494
528
|
);
|
|
495
529
|
})()}
|
|
496
530
|
</div>
|
|
531
|
+
</AnimateOnMount>
|
|
497
532
|
)}
|
|
498
533
|
|
|
499
534
|
</div>
|
|
500
535
|
</div>
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
536
|
+
</AnimateOnMount>
|
|
537
|
+
|
|
538
|
+
<AnimateOnMount delayMs={300} offsetY={6}>
|
|
539
|
+
<div className={'pt-8 text-sm text-center'} style={{ color: 'var(--text-secondary)' }}>
|
|
540
|
+
Report Completed: {new Date(updatedAt).toLocaleString(undefined, {
|
|
541
|
+
year: 'numeric',
|
|
542
|
+
month: 'long',
|
|
543
|
+
day: 'numeric',
|
|
544
|
+
hour: 'numeric',
|
|
545
|
+
minute: '2-digit',
|
|
546
|
+
timeZoneName: 'short',
|
|
547
|
+
})}
|
|
548
|
+
</div>
|
|
549
|
+
</AnimateOnMount>
|
|
512
550
|
</div>
|
|
513
551
|
</div>
|
|
552
|
+
</AnimateOnMount>
|
|
514
553
|
<footer className={'mt-12 pt-6 border-t'} style={{ borderColor: 'var(--icon-button-secondary)' }}>
|
|
515
554
|
<p className={'text-center text-xs max-w-4xl mx-auto'} style={{ color: 'var(--text-secondary)' }}>
|
|
516
555
|
© 2025 Know Your Developer, LLC. All rights reserved. KYD Self-Check™, and associated marks are trademarks of Know Your Developer, LLC. This document is confidential, proprietary, and intended solely for the individual or entity to whom it is addressed. Unauthorized use, disclosure, copying, or distribution of this document or any of its contents is strictly prohibited and may be unlawful. Know Your Developer, LLC assumes no responsibility or liability for any errors or omissions contained herein. Report validity subject to the terms and conditions stated on the official Know Your Developer website located at https://knowyourdeveloper.ai.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
|
|
5
|
+
type AnimateOnMountProps = {
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
delayMs?: number;
|
|
8
|
+
durationMs?: number;
|
|
9
|
+
offsetY?: number; // positive moves up during animation
|
|
10
|
+
className?: string;
|
|
11
|
+
style?: React.CSSProperties;
|
|
12
|
+
as?: 'div' | 'section' | 'span' | 'header' | 'footer' | 'main' | 'article' | 'aside' | 'nav';
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const AnimateOnMount: React.FC<AnimateOnMountProps> = ({
|
|
16
|
+
children,
|
|
17
|
+
delayMs = 60,
|
|
18
|
+
durationMs = 420,
|
|
19
|
+
offsetY = 10,
|
|
20
|
+
className,
|
|
21
|
+
style,
|
|
22
|
+
as = 'div',
|
|
23
|
+
}) => {
|
|
24
|
+
const [mounted, setMounted] = React.useState(false);
|
|
25
|
+
const reduceMotion = React.useMemo(() => {
|
|
26
|
+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return false;
|
|
27
|
+
try {
|
|
28
|
+
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}, []);
|
|
33
|
+
|
|
34
|
+
React.useEffect(() => {
|
|
35
|
+
if (reduceMotion) {
|
|
36
|
+
// Render immediately with no animation
|
|
37
|
+
setMounted(true);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const id = requestAnimationFrame(() => setMounted(true));
|
|
41
|
+
return () => cancelAnimationFrame(id);
|
|
42
|
+
}, [reduceMotion]);
|
|
43
|
+
|
|
44
|
+
const Component = as as any;
|
|
45
|
+
const transition = `opacity ${durationMs}ms ease, transform ${durationMs}ms ease`;
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<Component
|
|
49
|
+
className={className}
|
|
50
|
+
style={{
|
|
51
|
+
opacity: mounted ? 1 : 0,
|
|
52
|
+
transform: mounted || reduceMotion ? 'translateY(0px)' : `translateY(${offsetY}px)`,
|
|
53
|
+
transition: reduceMotion ? undefined : transition,
|
|
54
|
+
transitionDelay: reduceMotion ? undefined : `${delayMs}ms`,
|
|
55
|
+
willChange: reduceMotion ? undefined : 'opacity, transform',
|
|
56
|
+
...style,
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
{children}
|
|
60
|
+
</Component>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export default AnimateOnMount;
|
|
65
|
+
|
|
66
|
+
|
|
@@ -26,9 +26,9 @@ export default function RiskCard({
|
|
|
26
26
|
const pctGood = Math.max(0, Math.min(100, Math.round(Number(percentGood ?? 0))));
|
|
27
27
|
const displayLabel = label || '';
|
|
28
28
|
|
|
29
|
-
// bar heights
|
|
30
|
-
const bars = [
|
|
31
|
-
let activeIndex = 0; // Default to the
|
|
29
|
+
// bar heights ascending representation
|
|
30
|
+
const bars = [40, 60, 85, 110, 140];
|
|
31
|
+
let activeIndex = 0; // Default to the shortest bar (highest risk)
|
|
32
32
|
if (pctGood >= 80) {
|
|
33
33
|
activeIndex = 4;
|
|
34
34
|
} else if (pctGood >= 60) {
|