kyd-shared-badge 0.3.23 → 0.3.25

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.3.23",
3
+ "version": "0.3.25",
4
4
  "private": false,
5
5
  "main": "./src/index.ts",
6
6
  "module": "./src/index.ts",
@@ -160,7 +160,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
160
160
  </style>
161
161
  )}
162
162
  {/* Share controls removed; app-level pages render their own actions */}
163
- <Reveal offsetY={8} durationMs={500}>
163
+ <Reveal headless={isHeadless} offsetY={8} durationMs={500}>
164
164
  <ReportHeader
165
165
  badgeId={badgeId}
166
166
  developerName={badgeData.developerName}
@@ -184,8 +184,8 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
184
184
  >
185
185
  <div className={'space-y-12 divide-y'} style={{ borderColor: 'var(--icon-button-secondary)' }}>
186
186
  <div className="pt-8 first:pt-0 kyd-avoid-break">
187
- <Reveal as={'h4'} offsetY={8} durationMs={500} className={'text-2xl font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Report Summary</Reveal>
188
- <Reveal as={'div'} offsetY={8} durationMs={500} className={'space-y-12 divide-y'} style={{ borderColor: 'var(--icon-button-secondary)' }}>
187
+ <Reveal headless={isHeadless} as={'h4'} offsetY={8} durationMs={500} className={'text-2xl font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Report Summary</Reveal>
188
+ <Reveal headless={isHeadless} as={'div'} offsetY={8} durationMs={500} className={'space-y-12 divide-y'} style={{ borderColor: 'var(--icon-button-secondary)' }}>
189
189
  <div className="grid grid-cols-1 sm:grid-cols-3 gap-4 *:min-h-full kyd-avoid-break">
190
190
  {/* Technical semicircle gauge (refactored) */}
191
191
  {(() => {
@@ -251,9 +251,9 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
251
251
  {/* Technical Scores */}
252
252
  <div className="mt-8" >
253
253
  <div key={'Technical'} className='pt-8 space-y-8 kyd-avoid-break' style={{ borderColor: 'var(--icon-button-secondary)'}}>
254
- <Reveal as={'h4'} offsetY={8} className={'text-2xl font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>KYD Technical</Reveal>
254
+ <Reveal headless={isHeadless} as={'h4'} offsetY={8} className={'text-2xl font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>KYD Technical</Reveal>
255
255
  {/* technical graph insights */}
256
- <Reveal>
256
+ <Reveal headless={isHeadless}>
257
257
  <div className="">
258
258
  <GraphInsights
259
259
  graphInsights={graphInsights}
@@ -269,7 +269,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
269
269
  <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)'}}>
270
270
 
271
271
  {/* Left: Bars */}
272
- <Reveal className="lg:col-span-8 h-full">
272
+ <Reveal headless={isHeadless} className="lg:col-span-8 h-full">
273
273
  <CategoryBars
274
274
  title={'Technical Category Contributions'}
275
275
  categories={genreMapping?.['Technical'] as string[]}
@@ -281,7 +281,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
281
281
  </Reveal>
282
282
 
283
283
  {/* Right: Contributing Factors (hidden on small screens) */}
284
- <Reveal className="lg:col-span-4 w-full ml-0 lg:ml-20 hidden lg:flex flex-col items-start justify-start" delayMs={80}>
284
+ <Reveal headless={isHeadless} className="lg:col-span-4 w-full ml-0 lg:ml-20 hidden lg:flex flex-col items-start justify-start" delayMs={80}>
285
285
  <div className={'text-sm font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Top Contributing Factors</div>
286
286
  <div className="space-y-4">
287
287
  {(genreMapping?.['Technical'] || []).map((cat: string) => {
@@ -313,11 +313,11 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
313
313
  </Reveal>
314
314
  </div>
315
315
 
316
- <Reveal>
316
+ <Reveal headless={isHeadless}>
317
317
  <div className="pt-8 border-t kyd-avoid-break" style={{ borderColor: 'var(--icon-button-secondary)'}}>
318
318
  <h3 className={'text-xl font-bold mb-3 kyd-keep-with-next'} style={{ color: 'var(--text-main)' }}>KYD Technical - Skills Insights</h3>
319
319
  <div className={'prose prose-sm max-w-none mb-6 space-y-4'} style={{ color: 'var(--text-secondary)' }}>
320
- <Skills skillsMatrix={skillsMatrix} skillsCategoryRadar={graphInsights?.skillsCategoryRadar} />
320
+ <Skills skillsMatrix={skillsMatrix} skillsCategoryRadar={graphInsights?.skillsCategoryRadar} headless={isHeadless} />
321
321
  </div>
322
322
  </div>
323
323
  </Reveal>
@@ -328,10 +328,10 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
328
328
 
329
329
 
330
330
  <div className="pt-8 space-y-8">
331
- <Reveal as={'h3'} offsetY={8} className={`text-2xl font-bold ${isHeadless ? 'kyd-break-before' : ''}`} style={{ color: 'var(--text-main)' }}>KYD Risk - Overview</Reveal>
331
+ <Reveal headless={isHeadless} as={'h3'} offsetY={8} className={`text-2xl font-bold ${isHeadless ? 'kyd-break-before' : ''}`} style={{ color: 'var(--text-main)' }}>KYD Risk - Overview</Reveal>
332
332
 
333
333
  {/* Risk Graph Insights and Category Bars */}
334
- <Reveal>
334
+ <Reveal headless={isHeadless}>
335
335
  <div className="">
336
336
  <GraphInsights
337
337
  graphInsights={graphInsights}
@@ -344,7 +344,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
344
344
  </Reveal>
345
345
  <div className="grid grid-cols-1 lg:grid-cols-12 gap-8 w-full items-stretch py-8 border-y kyd-avoid-break" style={{ borderColor: 'var(--icon-button-secondary)' }}>
346
346
  {/* Left: Bars */}
347
- <Reveal className="lg:col-span-8 h-full">
347
+ <Reveal headless={isHeadless} className="lg:col-span-8 h-full">
348
348
  <CategoryBars
349
349
  title={'KYD Risk - Category Insights'}
350
350
  categories={genreMapping?.['Risk'] as string[]}
@@ -355,7 +355,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
355
355
  />
356
356
  </Reveal>
357
357
  {/* Right: Contributing Factors (hidden on small screens) */}
358
- <Reveal className="lg:col-span-4 w-full ml-0 lg:ml-20 hidden lg:flex flex-col items-start justify-start" delayMs={80}>
358
+ <Reveal headless={isHeadless} className="lg:col-span-4 w-full ml-0 lg:ml-20 hidden lg:flex flex-col items-start justify-start" delayMs={80}>
359
359
  <div className={'text-sm font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Top Contributing Factors</div>
360
360
  <div className="space-y-4">
361
361
  {genreMapping?.['Risk']?.map((cat: string) => {
@@ -389,7 +389,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
389
389
 
390
390
  {/* cyber risk display */}
391
391
  {badgeData.optOutScreening ? (
392
- <Reveal className={'p-4 rounded-lg border'} style={{ backgroundColor: 'var(--icon-button-secondary)', borderColor: 'var(--icon-button-secondary)' }}>
392
+ <Reveal headless={isHeadless} className={'p-4 rounded-lg border'} style={{ backgroundColor: 'var(--icon-button-secondary)', borderColor: 'var(--icon-button-secondary)' }}>
393
393
  <div className="flex items-start">
394
394
  <span className="h-5 w-5 mr-3 mt-0.5 flex-shrink-0" style={{ color: yellow }}>
395
395
  <FiAlertTriangle size={20} />
@@ -411,7 +411,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
411
411
  const fbiMatches = ss?.fbi_matches && (ss.fbi_matches.length > 0);
412
412
  if (!(ofacMatches || cslDetails || fbiMatches)) return null;
413
413
  return (
414
- <Reveal className={'mb-8 rounded-lg border p-4'} style={{ borderColor: 'var(--icon-button-secondary)', backgroundColor: 'var(--content-card-background)' }}>
414
+ <Reveal headless={isHeadless} className={'mb-8 rounded-lg border p-4'} style={{ borderColor: 'var(--icon-button-secondary)', backgroundColor: 'var(--content-card-background)' }}>
415
415
  <h4 className={'text-lg font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>3A. Sanctions Matches</h4>
416
416
  {/* OFAC matches */}
417
417
  {ofacMatches && (
@@ -474,7 +474,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
474
474
  </Reveal>
475
475
  );
476
476
  })()}
477
- <Reveal>
477
+ <Reveal headless={isHeadless}>
478
478
  <IpRiskAnalysisDisplay ipRiskAnalysis={screening_sources?.ip_risk_analysis} />
479
479
  </Reveal>
480
480
  </>
@@ -483,7 +483,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
483
483
  </div>
484
484
 
485
485
  {/* Connected Platforms */}
486
- <Reveal>
486
+ <Reveal headless={isHeadless}>
487
487
  <ConnectedPlatforms accounts={connected} />
488
488
  </Reveal>
489
489
 
@@ -493,7 +493,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
493
493
  <div className="space-y-8">
494
494
 
495
495
  {/* Skills */}
496
- <Reveal>
496
+ <Reveal headless={isHeadless}>
497
497
  <div>
498
498
  <h4 id="appendix-skills" className={'text-lg font-bold mb-1'} style={{ color: 'var(--text-main)' }}>Skills</h4>
499
499
  <div className="text-sm mb-4" style={{ color: 'var(--text-secondary)' }}>
@@ -505,7 +505,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
505
505
 
506
506
  {/* Observations */}
507
507
  {Array.isArray(graphInsights?.business_rules_all) && graphInsights.business_rules_all.length > 0 && (
508
- <Reveal>
508
+ <Reveal headless={isHeadless}>
509
509
  <div>
510
510
  <h4 className={'text-lg font-bold mb-4'} style={{ color: 'var(--text-main)' }}>Observations</h4>
511
511
  <AppendixTables
@@ -515,6 +515,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
515
515
  searchedAt={updatedAt}
516
516
  developerName={developerName || 'this developer'}
517
517
  genreMapping={genreMapping as Record<string, string[]>}
518
+ headless={isHeadless}
518
519
  />
519
520
  </div>
520
521
  </Reveal>
@@ -522,7 +523,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
522
523
 
523
524
  {/* Sanctions & Watchlists */}
524
525
  {!badgeData.optOutScreening && screening_sources && (
525
- <Reveal>
526
+ <Reveal headless={isHeadless}>
526
527
  <div>
527
528
  <h4 className={'text-lg font-bold mb-4'} style={{ color: 'var(--text-main)' }}>Sanctions & Watchlists</h4>
528
529
  {(() => {
@@ -542,6 +543,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
542
543
  sources={useDetailed ? detailed : dedup}
543
544
  searchedAt={updatedAt}
544
545
  developerName={developerName || 'this developer'}
546
+ headless={isHeadless}
545
547
  />
546
548
  );
547
549
  })()}
@@ -552,14 +554,14 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
552
554
  </div>
553
555
  </div>
554
556
 
555
- <Reveal>
557
+ <Reveal headless={isHeadless}>
556
558
  <div className={'pt-8 text-sm text-center'} style={{ color: 'var(--text-secondary)' }}>
557
559
  Report Completed: {formatLocalDateTime(updatedAt)}
558
560
  </div>
559
561
  </Reveal>
560
562
  </div>
561
563
  </div>
562
- <Reveal>
564
+ <Reveal headless={isHeadless}>
563
565
  <footer className={'mt-12 pt-6 border-t'} style={{ borderColor: 'var(--icon-button-secondary)' }}>
564
566
  <p className={'text-center text-xs max-w-4xl mx-auto'} style={{ color: 'var(--text-secondary)' }}>
565
567
  © 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.
@@ -39,6 +39,7 @@ interface AppendixTableProps {
39
39
  developerName: string;
40
40
  // Used for business_rules: map genres (e.g., Technical/Risk) to category arrays
41
41
  genreMapping?: Record<string, string[]>;
42
+ headless?: boolean;
42
43
  }
43
44
 
44
45
  const SanctionsRow = ({
@@ -48,6 +49,7 @@ const SanctionsRow = ({
48
49
  onToggle,
49
50
  expanded,
50
51
  colSpan,
52
+ headless,
51
53
  }: {
52
54
  source: SanctionSource;
53
55
  searchedAt: string;
@@ -55,13 +57,14 @@ const SanctionsRow = ({
55
57
  onToggle: () => void;
56
58
  expanded: boolean;
57
59
  colSpan: number;
60
+ headless?: boolean;
58
61
  }) => (
59
62
  <>
60
63
  <tr className={'transition-colors'} style={source.matched ? { backgroundColor: 'rgba(236,102,98,0.08)' } : undefined}>
61
64
  <td className={'px-4 py-4 whitespace-nowrap align-top text-sm font-medium'} style={{ color: 'var(--text-main)' }}>
62
65
  <div className="flex flex-col gap-1">
63
66
  <span>{source.issuingEntity}</span>
64
- {source.sublists && source.sublists.length > 0 && (
67
+ {source.sublists && source.sublists.length > 0 && !headless && (
65
68
  <button
66
69
  type="button"
67
70
  onClick={onToggle}
@@ -94,7 +97,7 @@ const SanctionsRow = ({
94
97
  : (<span>No exact match for <strong style={{ color: 'var(--text-main)' }}>{developerName}</strong> was found on this list.</span>)}
95
98
  </td>
96
99
  </tr>
97
- {expanded && source.sublists && source.sublists.length > 0 && (
100
+ {(headless || expanded) && source.sublists && source.sublists.length > 0 && (
98
101
  <tr>
99
102
  <td colSpan={colSpan} className={'px-6 pb-4'}>
100
103
  <div className={'mt-1 border-t pt-3'} style={{ borderColor: 'var(--icon-button-secondary)' }}>
@@ -128,7 +131,7 @@ const DomainRow = ({ source, searchedAt, developerName }: { source: DomainSource
128
131
  );
129
132
 
130
133
 
131
- const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedAt, developerName, genreMapping }) => {
134
+ const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedAt, developerName, genreMapping, headless }) => {
132
135
  const [visibleCount, setVisibleCount] = useState(PAGE_SIZE);
133
136
  const [expanded, setExpanded] = useState<{ [k: number]: boolean }>({});
134
137
 
@@ -227,7 +230,7 @@ const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedA
227
230
 
228
231
  const visibleParsedSources = (type === 'business_rules')
229
232
  ? sortedParsedSources
230
- : sortedParsedSources.slice(0, visibleCount);
233
+ : (headless ? sortedParsedSources : sortedParsedSources.slice(0, visibleCount));
231
234
 
232
235
  const handleLoadMore = () => {
233
236
  setVisibleCount(currentCount => currentCount + PAGE_SIZE);
@@ -238,8 +241,8 @@ const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedA
238
241
  }
239
242
 
240
243
  return (
241
- <div>
242
- <div className={'overflow-x-auto rounded-lg border'} style={{ borderColor: 'var(--icon-button-secondary)', backgroundColor: 'var(--content-card-background)' }}>
244
+ <div className={'kyd-avoid-break'}>
245
+ <div className={'overflow-x-auto rounded-lg border kyd-avoid-break'} style={{ borderColor: 'var(--icon-button-secondary)', backgroundColor: 'var(--content-card-background)', breakInside: 'avoid', pageBreakInside: 'avoid' as unknown as undefined }}>
243
246
  <table className={'min-w-full'}>
244
247
  <colgroup>
245
248
  {headers.map((_, idx) => (
@@ -268,6 +271,7 @@ const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedA
268
271
  onToggle={() => setExpanded(prev => ({ ...prev, [index]: !prev[index] }))}
269
272
  expanded={!!expanded[index]}
270
273
  colSpan={headers.length}
274
+ headless={headless}
271
275
  />
272
276
  );
273
277
  }
@@ -311,7 +315,7 @@ const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedA
311
315
  </tbody>
312
316
  </table>
313
317
  </div>
314
- {type !== 'business_rules' && parsedSources.length > PAGE_SIZE && (
318
+ {type !== 'business_rules' && parsedSources.length > PAGE_SIZE && !headless && (
315
319
  <div className={'mt-4 flex items-center justify-between text-sm'} style={{ color: 'var(--text-secondary)' }}>
316
320
  <p>
317
321
  Showing {Math.min(visibleCount, parsedSources.length)} of {parsedSources.length} entries
@@ -21,6 +21,8 @@ type RevealProps = PropsWithChildren<{
21
21
  threshold?: number | number[];
22
22
  /** Root margin for preloading */
23
23
  rootMargin?: string;
24
+ /** When true, disable animations and render normally */
25
+ headless?: boolean;
24
26
  }>;
25
27
 
26
28
  const Reveal: React.FC<RevealProps> = ({
@@ -33,11 +35,21 @@ const Reveal: React.FC<RevealProps> = ({
33
35
  style,
34
36
  threshold,
35
37
  rootMargin,
38
+ headless,
36
39
  children,
37
40
  }) => {
38
41
  const Tag = as as any;
39
42
  const { ref, hasIntersected } = useInViewOnce({ threshold, rootMargin });
40
43
 
44
+ // In headless mode, render without any animation or transition.
45
+ if (headless) {
46
+ return (
47
+ <Tag ref={ref} className={className} style={style}>
48
+ {children}
49
+ </Tag>
50
+ );
51
+ }
52
+
41
53
  // Respect reduced motion is handled in the hook by triggering immediately.
42
54
  const baseTransform = `translate(${offsetX}px, ${offsetY}px)`;
43
55
  const eased = 'cubic-bezier(0.22, 1, 0.36, 1)'; // subtle spring-ish ease
@@ -166,7 +166,7 @@ const TooltipBox = ({ state }: { state: HoverTooltipState }) => {
166
166
  );
167
167
  };
168
168
 
169
- const Skills = ({ skillsCategoryRadar }: { skillsMatrix?: SkillsMatrix; skillsCategoryRadar?: SkillsRadarPoint[] }) => {
169
+ const Skills = ({ skillsCategoryRadar, headless }: { skillsMatrix?: SkillsMatrix; skillsCategoryRadar?: SkillsRadarPoint[]; headless?: boolean }) => {
170
170
  // const hasMatrix = !!(skillsMatrix && Array.isArray(skillsMatrix.skills) && skillsMatrix.skills.length > 0);
171
171
  const hasRadar = !!(skillsCategoryRadar && skillsCategoryRadar.length > 0);
172
172
 
@@ -180,6 +180,16 @@ const Skills = ({ skillsCategoryRadar }: { skillsMatrix?: SkillsMatrix; skillsCa
180
180
  const [footprintLegendTooltip, setFootprintLegendTooltip] = useState<HoverTooltipState>(null);
181
181
  const [barLegendTooltip, setBarLegendTooltip] = useState<HoverTooltipState>(null);
182
182
 
183
+ const disableAnimations = useMemo(() => {
184
+ if (headless) return true;
185
+ if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {
186
+ try {
187
+ return window.matchMedia('print').matches;
188
+ } catch {}
189
+ }
190
+ return false;
191
+ }, [headless]);
192
+
183
193
  useEffect(() => {
184
194
  const measure = () => setContainerWidth(containerRef.current?.clientWidth || 0);
185
195
  measure();
@@ -187,6 +197,16 @@ const Skills = ({ skillsCategoryRadar }: { skillsMatrix?: SkillsMatrix; skillsCa
187
197
  return () => window.removeEventListener('resize', measure);
188
198
  }, []);
189
199
 
200
+ // Nudge layout for headless/print contexts so responsive containers measure correctly
201
+ useEffect(() => {
202
+ if (typeof window !== 'undefined') {
203
+ const id = window.setTimeout(() => {
204
+ try { window.dispatchEvent(new Event('resize')); } catch {}
205
+ }, 0);
206
+ return () => window.clearTimeout(id);
207
+ }
208
+ }, []);
209
+
190
210
  const combinedBubbleData: BubbleDatum[] = useMemo(() => {
191
211
  return skillsRadarLimited.map((d) => {
192
212
  const vals = [Number(d.observed || 0), Number(d.self_reported || 0), Number(d.certified || 0)];
@@ -210,11 +230,11 @@ const Skills = ({ skillsCategoryRadar }: { skillsMatrix?: SkillsMatrix; skillsCa
210
230
 
211
231
  {/* Skills Coverage and Breakdown: two charts side-by-side */}
212
232
  {hasRadar && (
213
- <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
214
- <div className={'rounded-lg p-4 border'} style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)' }}>
233
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6 kyd-avoid-break">
234
+ <div className={'rounded-lg p-4 border kyd-avoid-break'} style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)', breakInside: 'avoid', pageBreakInside: 'avoid' as unknown as undefined }}>
215
235
  <h4 className={'font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Skills Footprint</h4>
216
236
  <p className={'text-sm mb-4'} style={{ color: 'var(--text-secondary)' }}>The bubble chart visualizes individual skills, where bubble size reflects the weight of supporting evidence and placement indicates relative strength across the skill set.</p>
217
- <div ref={containerRef} style={{ width: '100%', height: 340, position: 'relative' }}>
237
+ <div ref={containerRef} className={'kyd-avoid-break'} style={{ width: '100%', height: 340, position: 'relative', breakInside: 'avoid', pageBreakInside: 'avoid' as unknown as undefined }}>
218
238
  {(() => {
219
239
  const width = containerWidth || 600;
220
240
  const height = 300;
@@ -276,12 +296,12 @@ const Skills = ({ skillsCategoryRadar }: { skillsMatrix?: SkillsMatrix; skillsCa
276
296
  </svg>
277
297
  );
278
298
  })()}
279
- <TooltipBox state={footprintChartTooltip} />
299
+ {!headless && <TooltipBox state={footprintChartTooltip} />}
280
300
  </div>
281
301
  {/* Legend */}
282
302
  <div className={'mt-3'}>
283
303
  {/* <div className={'text-xs mb-2'} style={{ color: 'var(--text-secondary)' }}>Legend</div> */}
284
- <div ref={footprintLegendRef} style={{ position: 'relative' }}>
304
+ <div ref={footprintLegendRef} className={'kyd-avoid-break'} style={{ position: 'relative', breakInside: 'avoid', pageBreakInside: 'avoid' as unknown as undefined }}>
285
305
  <div className="grid grid-cols-1 sm:grid-cols-2 gap-x-4 gap-y-1">
286
306
  {legendData.map((item, idx) => (
287
307
  <div
@@ -314,11 +334,11 @@ const Skills = ({ skillsCategoryRadar }: { skillsMatrix?: SkillsMatrix; skillsCa
314
334
  </div>
315
335
  ))}
316
336
  </div>
317
- <TooltipBox state={footprintLegendTooltip} />
337
+ {!headless && <TooltipBox state={footprintLegendTooltip} />}
318
338
  </div>
319
339
  </div>
320
340
  </div>
321
- <div className={'rounded-lg p-4 border'} style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)' }}>
341
+ <div className={'rounded-lg p-4 border kyd-avoid-break'} style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)', breakInside: 'avoid', pageBreakInside: 'avoid' as unknown as undefined }}>
322
342
  <h4 className={'font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Skills by Validation Type</h4>
323
343
  <p className={'text-sm mb-4'} style={{ color: 'var(--text-secondary)' }}>The bar chart shows how each skill is supported by self-attested claims, observed practice, or certified evidence.</p>
324
344
  <div className="space-y-6 flex-col items-center" >
@@ -330,9 +350,9 @@ const Skills = ({ skillsCategoryRadar }: { skillsMatrix?: SkillsMatrix; skillsCa
330
350
  <XAxis dataKey="axis" tick={{ fill: 'var(--text-secondary)', fontSize: 12 }} interval={0} angle={-20} textAnchor="end" height={50} />
331
351
  <YAxis domain={[0, 100]} tick={{ fill: 'var(--text-secondary)' }} />
332
352
  <Tooltip contentStyle={{ background: 'var(--content-card-background)', border: `1px solid var(--icon-button-secondary)`, color: 'var(--text-main)' }} />
333
- <Bar dataKey="observed" name="Observed" fill={'var(--bar-observed)'} />
334
- <Bar dataKey="self_reported" name="Self-reported" fill={'var(--bar-self-reported)'} />
335
- <Bar dataKey="certified" name="Certified" fill={'var(--bar-certified)'} />
353
+ <Bar dataKey="observed" name="Observed" fill={'var(--bar-observed)'} isAnimationActive={!disableAnimations} />
354
+ <Bar dataKey="self_reported" name="Self-reported" fill={'var(--bar-self-reported)'} isAnimationActive={!disableAnimations} />
355
+ <Bar dataKey="certified" name="Certified" fill={'var(--bar-certified)'} isAnimationActive={!disableAnimations} />
336
356
  </BarChart>
337
357
  </ResponsiveContainer>
338
358
  </div>
@@ -409,7 +429,7 @@ const Skills = ({ skillsCategoryRadar }: { skillsMatrix?: SkillsMatrix; skillsCa
409
429
  <span>Certified</span>
410
430
  </div>
411
431
  </div>
412
- <TooltipBox state={barLegendTooltip} />
432
+ {!headless && <TooltipBox state={barLegendTooltip} />}
413
433
  </div>
414
434
  </div>
415
435
  </div>