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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kyd-shared-badge",
3
- "version": "0.2.28",
3
+ "version": "0.2.29",
4
4
  "private": false,
5
5
  "main": "./src/index.ts",
6
6
  "module": "./src/index.ts",
@@ -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
- <ReportHeader
142
- badgeId={badgeId}
143
- developerName={badgeData.developerName}
144
- updatedAt={updatedAt}
145
- score={overallFinalPercent || 0}
146
- isPublic={true}
147
- badgeImageUrl={badgeData.badgeImageUrl || ''}
148
- summary={report_summary}
149
- countries={(assessmentResult?.screening_sources?.ip_risk_analysis?.raw_data?.countries) || []}
150
- />
151
- <div
152
- className={'rounded-xl shadow-xl p-6 sm:p-8 mt-8 border'}
153
- style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)' }}
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
- <GaugeCard
168
- key={'technical-card'}
169
- title={'KYD Technical'}
170
- description={'The gauge visualization shows a weighted composite of technical evidence, with rightward movement indicating stronger indications of developer capability'}
171
- percent={pct}
172
- label={label}
173
- topMovers={top?.map(t => ({ label: t?.label, uid: t?.uid }))}
174
- topMoversTitle={'Top Score Movers'}
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
- <RiskCard
188
- title={'KYD Risk'}
189
- description={'The bar chart visualizes relative risk levels, where shorter bars denote lower risk and taller bars indicate greater exposure.'}
190
- percentGood={pctGood}
191
- label={label}
192
- topMovers={top?.map(t => ({ label: t?.label, uid: t?.uid }))}
193
- topMoversTitle={'Top Score Movers'}
194
- tooltipText={tooltip}
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
- <GaugeCard
206
- key={'ai-card'}
207
- title={'KYD AI'}
208
- description={'Indicates the degree to which AI-assisted code is explicitly disclosed across analyzed files.'}
209
- percent={ai_usage_summary?.transparency_score}
210
- label={label}
211
- // id non-functional
212
- topMovers={topMovers.map(t => ({ label: t, uid: 'ai-usage' }))}
213
- topMoversTitle={'Key Findings'}
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
- <div className="">
226
- <GraphInsights
227
- graphInsights={graphInsights}
228
- categories={genreMapping?.['Technical'] as string[]}
229
- genre={'Technical'}
230
- scoringSummary={scoringSummary}
231
- />
232
- </div>
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
- <div className="pt-8 border-t" style={{ borderColor: 'var(--icon-button-secondary)'}}>
283
- <h3 className={'text-xl font-bold mb-3'} style={{ color: 'var(--text-main)' }}>KYD Technical - Skills Insights</h3>
284
- <div className={'prose prose-sm max-w-none mb-6 space-y-4'} style={{ color: 'var(--text-secondary)' }}>
285
- <Skills skillsMatrix={skillsMatrix} skillsCategoryRadar={graphInsights?.skillsCategoryRadar} />
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
- </div>
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
- <div className="">
299
- <GraphInsights
300
- graphInsights={graphInsights}
301
- categories={genreMapping?.['Risk'] as string[]}
302
- genre={'Risk'}
303
- scoringSummary={scoringSummary}
304
- />
305
- </div>
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
- <IpRiskAnalysisDisplay ipRiskAnalysis={screening_sources?.ip_risk_analysis} />
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
- <ConnectedPlatforms accounts={connected} />
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
- <div>
454
- <h4 id="appendix-skills" className={'text-lg font-bold mb-4'} style={{ color: 'var(--text-main)' }}>Skills</h4>
455
- <SkillsAppendixTable skillsAll={skillsAll} />
456
- </div>
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
- <div>
461
- <h4 className={'text-lg font-bold mb-4'} style={{ color: 'var(--text-main)' }}>Observations</h4>
462
- <AppendixTables
463
- type="business_rules"
464
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
465
- sources={graphInsights.business_rules_all as any}
466
- searchedAt={updatedAt}
467
- developerName={developerName || 'this developer'}
468
- />
469
- </div>
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
- <div>
475
- <h4 className={'text-lg font-bold mb-4'} style={{ color: 'var(--text-main)' }}>Sanctions & Watchlists</h4>
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
- <div className={'pt-8 text-sm text-center'} style={{ color: 'var(--text-secondary)' }}>
503
- Report Completed: {new Date(updatedAt).toLocaleString(undefined, {
504
- year: 'numeric',
505
- month: 'long',
506
- day: 'numeric',
507
- hour: 'numeric',
508
- minute: '2-digit',
509
- timeZoneName: 'short',
510
- })}
511
- </div>
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 descending representation
30
- const bars = [140, 110, 85, 60, 40];
31
- let activeIndex = 0; // Default to the tallest bar (highest risk)
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) {