@ytspar/devbar 1.7.5-canary.16117ee → 1.8.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 (45) hide show
  1. package/dist/accessibility.js +2 -2
  2. package/dist/accessibility.js.map +1 -1
  3. package/dist/constants.d.ts +8 -0
  4. package/dist/constants.d.ts.map +1 -1
  5. package/dist/constants.js +12 -0
  6. package/dist/constants.js.map +1 -1
  7. package/dist/debug.js.map +1 -1
  8. package/dist/index.d.ts +4 -4
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +4 -4
  11. package/dist/index.js.map +1 -1
  12. package/dist/modules/rendering/collapsed.js +2 -2
  13. package/dist/modules/rendering/collapsed.js.map +1 -1
  14. package/dist/modules/rendering/compact.js +2 -2
  15. package/dist/modules/rendering/compact.js.map +1 -1
  16. package/dist/modules/rendering/console.js +1 -1
  17. package/dist/modules/rendering/console.js.map +1 -1
  18. package/dist/modules/rendering/expanded.d.ts.map +1 -1
  19. package/dist/modules/rendering/expanded.js +2 -2
  20. package/dist/modules/rendering/expanded.js.map +1 -1
  21. package/dist/modules/rendering/modals.d.ts.map +1 -1
  22. package/dist/modules/rendering/modals.js +252 -246
  23. package/dist/modules/rendering/modals.js.map +1 -1
  24. package/dist/modules/rendering/settings.js +41 -33
  25. package/dist/modules/rendering/settings.js.map +1 -1
  26. package/dist/modules/tooltips.js +2 -2
  27. package/dist/modules/tooltips.js.map +1 -1
  28. package/dist/network.js +1 -1
  29. package/dist/network.js.map +1 -1
  30. package/dist/storage.d.ts.map +1 -1
  31. package/dist/storage.js +1 -11
  32. package/dist/storage.js.map +1 -1
  33. package/dist/ui/cards.js +1 -1
  34. package/dist/ui/cards.js.map +1 -1
  35. package/dist/ui/index.d.ts +1 -1
  36. package/dist/ui/index.d.ts.map +1 -1
  37. package/dist/ui/index.js +1 -1
  38. package/dist/ui/index.js.map +1 -1
  39. package/dist/ui/modals.js +2 -2
  40. package/dist/ui/modals.js.map +1 -1
  41. package/dist/utils.d.ts +2 -0
  42. package/dist/utils.d.ts.map +1 -1
  43. package/dist/utils.js +10 -0
  44. package/dist/utils.js.map +1 -1
  45. package/package.json +10 -11
@@ -91,7 +91,7 @@ function renderOutlineNodes(nodes, parentEl, depth, headingTracker) {
91
91
  }
92
92
  const textSpan = document.createElement('span');
93
93
  Object.assign(textSpan.style, {
94
- color: '#d1d5db',
94
+ color: CSS_COLORS.textSecondary,
95
95
  fontSize: '0.6875rem',
96
96
  marginLeft: '8px',
97
97
  });
@@ -434,139 +434,178 @@ function faviconDevice(label, size) {
434
434
  return { text: 'Browser tab', color: CSS_COLORS.textSecondary };
435
435
  return { text: 'General', color: CSS_COLORS.textMuted };
436
436
  }
437
+ /** Create a single favicon card row with thumbnail, label, device pill, dimensions, and URL. */
438
+ function createFaviconCard(icon, index) {
439
+ const device = faviconDevice(icon.label, icon.size);
440
+ const row = document.createElement('div');
441
+ Object.assign(row.style, {
442
+ display: 'flex',
443
+ alignItems: 'center',
444
+ padding: '6px 8px',
445
+ gap: '10px',
446
+ borderRadius: '3px',
447
+ backgroundColor: index % 2 === 0 ? 'rgba(255, 255, 255, 0.02)' : 'transparent',
448
+ });
449
+ // Thumbnail frame
450
+ const frame = document.createElement('div');
451
+ Object.assign(frame.style, {
452
+ width: '32px',
453
+ height: '32px',
454
+ display: 'flex',
455
+ alignItems: 'center',
456
+ justifyContent: 'center',
457
+ backgroundColor: 'rgba(0, 0, 0, 0.25)',
458
+ border: '1px solid rgba(255, 255, 255, 0.06)',
459
+ borderRadius: '4px',
460
+ flexShrink: '0',
461
+ });
462
+ const thumb = document.createElement('img');
463
+ Object.assign(thumb.style, {
464
+ width: '22px',
465
+ height: '22px',
466
+ objectFit: 'contain',
467
+ });
468
+ thumb.src = icon.url;
469
+ thumb.alt = icon.label;
470
+ thumb.onerror = () => { frame.style.opacity = '0.3'; };
471
+ frame.appendChild(thumb);
472
+ row.appendChild(frame);
473
+ // Info column: label, device, dimensions + URL
474
+ const infoCol = document.createElement('div');
475
+ Object.assign(infoCol.style, {
476
+ flex: '1',
477
+ minWidth: '0',
478
+ display: 'flex',
479
+ flexDirection: 'column',
480
+ gap: '2px',
481
+ });
482
+ // Top row: label + device pill
483
+ const topRow = document.createElement('div');
484
+ Object.assign(topRow.style, {
485
+ display: 'flex',
486
+ alignItems: 'center',
487
+ gap: '6px',
488
+ });
489
+ const labelEl = document.createElement('span');
490
+ Object.assign(labelEl.style, {
491
+ color: CSS_COLORS.text,
492
+ fontSize: '0.6875rem',
493
+ fontWeight: '500',
494
+ overflow: 'hidden',
495
+ textOverflow: 'ellipsis',
496
+ whiteSpace: 'nowrap',
497
+ });
498
+ labelEl.textContent = icon.label;
499
+ if (icon.label.length > 24)
500
+ labelEl.title = icon.label;
501
+ topRow.appendChild(labelEl);
502
+ const devicePill = document.createElement('span');
503
+ Object.assign(devicePill.style, {
504
+ color: device.color,
505
+ fontSize: '0.5rem',
506
+ backgroundColor: withAlpha(device.color, 7),
507
+ padding: '1px 6px',
508
+ borderRadius: '6px',
509
+ letterSpacing: '0.03em',
510
+ whiteSpace: 'nowrap',
511
+ flexShrink: '0',
512
+ });
513
+ devicePill.textContent = device.text;
514
+ topRow.appendChild(devicePill);
515
+ infoCol.appendChild(topRow);
516
+ // Bottom row: declared size + actual dimensions + URL
517
+ const bottomRow = document.createElement('div');
518
+ Object.assign(bottomRow.style, {
519
+ display: 'flex',
520
+ alignItems: 'center',
521
+ gap: '6px',
522
+ fontSize: '0.5625rem',
523
+ color: CSS_COLORS.textMuted,
524
+ });
525
+ if (icon.size) {
526
+ const declaredEl = document.createElement('span');
527
+ declaredEl.textContent = icon.size;
528
+ declaredEl.style.opacity = '0.8';
529
+ bottomRow.appendChild(declaredEl);
530
+ }
531
+ // Actual dimensions (populated on load)
532
+ const dimEl = document.createElement('span');
533
+ dimEl.style.letterSpacing = '0.02em';
534
+ bottomRow.appendChild(dimEl);
535
+ thumb.onload = () => {
536
+ if (thumb.naturalWidth) {
537
+ const actual = `${thumb.naturalWidth}\u00d7${thumb.naturalHeight}`;
538
+ if (icon.size) {
539
+ dimEl.textContent = `\u2192 ${actual}`;
540
+ }
541
+ else {
542
+ dimEl.textContent = actual;
543
+ }
544
+ }
545
+ };
546
+ const sep = document.createElement('span');
547
+ sep.textContent = '\u00b7';
548
+ sep.style.opacity = '0.4';
549
+ bottomRow.appendChild(sep);
550
+ const urlEl = document.createElement('span');
551
+ Object.assign(urlEl.style, {
552
+ overflow: 'hidden',
553
+ textOverflow: 'ellipsis',
554
+ whiteSpace: 'nowrap',
555
+ opacity: '0.6',
556
+ });
557
+ urlEl.textContent = icon.url;
558
+ urlEl.title = icon.url;
559
+ bottomRow.appendChild(urlEl);
560
+ infoCol.appendChild(bottomRow);
561
+ row.appendChild(infoCol);
562
+ return row;
563
+ }
437
564
  function renderFaviconsSection(container, icons) {
438
565
  const color = CSS_COLORS.purple;
439
566
  const section = document.createElement('div');
440
567
  section.style.marginBottom = '20px';
441
568
  renderSchemaSectionHeader(section, 'Favicons', color, icons.length);
442
569
  icons.forEach((icon, i) => {
443
- const device = faviconDevice(icon.label, icon.size);
444
- const row = document.createElement('div');
445
- Object.assign(row.style, {
446
- display: 'flex',
447
- alignItems: 'center',
448
- padding: '6px 8px',
449
- gap: '10px',
450
- borderRadius: '3px',
451
- backgroundColor: i % 2 === 0 ? 'rgba(255, 255, 255, 0.02)' : 'transparent',
452
- });
453
- // Thumbnail frame
454
- const frame = document.createElement('div');
455
- Object.assign(frame.style, {
456
- width: '32px',
457
- height: '32px',
458
- display: 'flex',
459
- alignItems: 'center',
460
- justifyContent: 'center',
461
- backgroundColor: 'rgba(0, 0, 0, 0.25)',
462
- border: '1px solid rgba(255, 255, 255, 0.06)',
463
- borderRadius: '4px',
464
- flexShrink: '0',
465
- });
466
- const thumb = document.createElement('img');
467
- Object.assign(thumb.style, {
468
- width: '22px',
469
- height: '22px',
470
- objectFit: 'contain',
471
- });
472
- thumb.src = icon.url;
473
- thumb.alt = icon.label;
474
- thumb.onerror = () => { frame.style.opacity = '0.3'; };
475
- frame.appendChild(thumb);
476
- row.appendChild(frame);
477
- // Info column: label, device, dimensions + URL
478
- const infoCol = document.createElement('div');
479
- Object.assign(infoCol.style, {
480
- flex: '1',
481
- minWidth: '0',
482
- display: 'flex',
483
- flexDirection: 'column',
484
- gap: '2px',
485
- });
486
- // Top row: label + device pill
487
- const topRow = document.createElement('div');
488
- Object.assign(topRow.style, {
489
- display: 'flex',
490
- alignItems: 'center',
491
- gap: '6px',
492
- });
493
- const labelEl = document.createElement('span');
494
- Object.assign(labelEl.style, {
495
- color: CSS_COLORS.text,
496
- fontSize: '0.6875rem',
497
- fontWeight: '500',
498
- overflow: 'hidden',
499
- textOverflow: 'ellipsis',
500
- whiteSpace: 'nowrap',
501
- });
502
- labelEl.textContent = icon.label;
503
- if (icon.label.length > 24)
504
- labelEl.title = icon.label;
505
- topRow.appendChild(labelEl);
506
- const devicePill = document.createElement('span');
507
- Object.assign(devicePill.style, {
508
- color: device.color,
509
- fontSize: '0.5rem',
510
- backgroundColor: withAlpha(device.color, 7),
511
- padding: '1px 6px',
512
- borderRadius: '6px',
513
- letterSpacing: '0.03em',
514
- whiteSpace: 'nowrap',
515
- flexShrink: '0',
516
- });
517
- devicePill.textContent = device.text;
518
- topRow.appendChild(devicePill);
519
- infoCol.appendChild(topRow);
520
- // Bottom row: declared size + actual dimensions + URL
521
- const bottomRow = document.createElement('div');
522
- Object.assign(bottomRow.style, {
523
- display: 'flex',
524
- alignItems: 'center',
525
- gap: '6px',
526
- fontSize: '0.5625rem',
527
- color: CSS_COLORS.textMuted,
528
- });
529
- if (icon.size) {
530
- const declaredEl = document.createElement('span');
531
- declaredEl.textContent = icon.size;
532
- declaredEl.style.opacity = '0.8';
533
- bottomRow.appendChild(declaredEl);
534
- }
535
- // Actual dimensions (populated on load)
536
- const dimEl = document.createElement('span');
537
- dimEl.style.letterSpacing = '0.02em';
538
- bottomRow.appendChild(dimEl);
539
- thumb.onload = () => {
540
- if (thumb.naturalWidth) {
541
- const actual = `${thumb.naturalWidth}\u00d7${thumb.naturalHeight}`;
542
- if (icon.size) {
543
- dimEl.textContent = `\u2192 ${actual}`;
544
- }
545
- else {
546
- dimEl.textContent = actual;
547
- }
548
- }
549
- };
550
- const sep = document.createElement('span');
551
- sep.textContent = '\u00b7';
552
- sep.style.opacity = '0.4';
553
- bottomRow.appendChild(sep);
554
- const urlEl = document.createElement('span');
555
- Object.assign(urlEl.style, {
556
- overflow: 'hidden',
557
- textOverflow: 'ellipsis',
558
- whiteSpace: 'nowrap',
559
- opacity: '0.6',
560
- });
561
- urlEl.textContent = icon.url;
562
- urlEl.title = icon.url;
563
- bottomRow.appendChild(urlEl);
564
- infoCol.appendChild(bottomRow);
565
- row.appendChild(infoCol);
566
- section.appendChild(row);
570
+ section.appendChild(createFaviconCard(icon, i));
567
571
  });
568
572
  container.appendChild(section);
569
573
  }
574
+ /** Create a colored summary pill (e.g. "3 errors", "2 warnings"). */
575
+ function createSummaryPill(text, color) {
576
+ const pill = document.createElement('span');
577
+ Object.assign(pill.style, {
578
+ color,
579
+ fontSize: '0.5625rem',
580
+ backgroundColor: withAlpha(color, 8),
581
+ padding: '2px 8px',
582
+ borderRadius: '8px',
583
+ letterSpacing: '0.03em',
584
+ });
585
+ pill.textContent = text;
586
+ return pill;
587
+ }
588
+ /** Create a striped row with alternating background and optional left border. */
589
+ function createStripedRow(index, borderColor) {
590
+ const row = document.createElement('div');
591
+ Object.assign(row.style, {
592
+ display: 'flex',
593
+ alignItems: 'center',
594
+ padding: '4px 8px',
595
+ gap: '8px',
596
+ borderRadius: '3px',
597
+ backgroundColor: index % 2 === 0 ? 'rgba(255, 255, 255, 0.02)' : 'transparent',
598
+ ...(borderColor ? { borderLeft: `2px solid ${withAlpha(borderColor, 25)}` } : {}),
599
+ });
600
+ return row;
601
+ }
602
+ /** Create a styled text span for modal info items. */
603
+ function createInfoSpan(text, styles) {
604
+ const el = document.createElement('span');
605
+ Object.assign(el.style, styles);
606
+ el.textContent = text;
607
+ return el;
608
+ }
570
609
  function renderMissingTagsSection(container, tags) {
571
610
  const section = document.createElement('div');
572
611
  section.style.marginBottom = '20px';
@@ -584,76 +623,39 @@ function renderMissingTagsSection(container, tags) {
584
623
  marginBottom: '8px',
585
624
  });
586
625
  if (errorCount > 0) {
587
- const errPill = document.createElement('span');
588
- Object.assign(errPill.style, {
589
- color: CSS_COLORS.error,
590
- fontSize: '0.5625rem',
591
- backgroundColor: withAlpha(CSS_COLORS.error, 8),
592
- padding: '2px 8px',
593
- borderRadius: '8px',
594
- letterSpacing: '0.03em',
595
- });
596
- errPill.textContent = `${errorCount} error${errorCount > 1 ? 's' : ''}`;
597
- summary.appendChild(errPill);
626
+ summary.appendChild(createSummaryPill(`${errorCount} error${errorCount > 1 ? 's' : ''}`, CSS_COLORS.error));
598
627
  }
599
628
  if (warnCount > 0) {
600
- const warnPill = document.createElement('span');
601
- Object.assign(warnPill.style, {
602
- color: CSS_COLORS.warning,
603
- fontSize: '0.5625rem',
604
- backgroundColor: withAlpha(CSS_COLORS.warning, 8),
605
- padding: '2px 8px',
606
- borderRadius: '8px',
607
- letterSpacing: '0.03em',
608
- });
609
- warnPill.textContent = `${warnCount} warning${warnCount > 1 ? 's' : ''}`;
610
- summary.appendChild(warnPill);
629
+ summary.appendChild(createSummaryPill(`${warnCount} warning${warnCount > 1 ? 's' : ''}`, CSS_COLORS.warning));
611
630
  }
612
631
  section.appendChild(summary);
613
632
  }
614
633
  tags.forEach((tag, i) => {
615
634
  const isError = tag.severity === 'error';
616
635
  const tagColor = isError ? CSS_COLORS.error : CSS_COLORS.warning;
617
- const row = document.createElement('div');
618
- Object.assign(row.style, {
619
- display: 'flex',
620
- alignItems: 'center',
621
- padding: '4px 8px',
622
- gap: '8px',
623
- borderRadius: '3px',
624
- backgroundColor: i % 2 === 0 ? 'rgba(255, 255, 255, 0.02)' : 'transparent',
625
- borderLeft: `2px solid ${withAlpha(tagColor, 25)}`,
626
- });
627
- const icon = document.createElement('span');
628
- Object.assign(icon.style, {
636
+ const row = createStripedRow(i, tagColor);
637
+ const icon = createInfoSpan(isError ? '\u2718' : '\u26a0', {
629
638
  fontSize: '0.625rem',
630
639
  flexShrink: '0',
631
640
  width: '14px',
632
641
  textAlign: 'center',
633
642
  color: tagColor,
634
643
  });
635
- icon.textContent = isError ? '\u2718' : '\u26a0';
636
644
  row.appendChild(icon);
637
- const tagName = document.createElement('span');
638
- Object.assign(tagName.style, {
645
+ row.appendChild(createInfoSpan(tag.tag, {
639
646
  color: CSS_COLORS.text,
640
647
  fontSize: '0.6875rem',
641
648
  width: '120px',
642
649
  minWidth: '120px',
643
650
  flexShrink: '0',
644
651
  fontWeight: '500',
645
- });
646
- tagName.textContent = tag.tag;
647
- row.appendChild(tagName);
648
- const hint = document.createElement('span');
649
- Object.assign(hint.style, {
652
+ }));
653
+ row.appendChild(createInfoSpan(tag.hint, {
650
654
  color: CSS_COLORS.textMuted,
651
655
  fontSize: '0.6875rem',
652
656
  flex: '1',
653
657
  opacity: '0.85',
654
- });
655
- hint.textContent = tag.hint;
656
- row.appendChild(hint);
658
+ }));
657
659
  section.appendChild(row);
658
660
  });
659
661
  container.appendChild(section);
@@ -836,6 +838,85 @@ function createViolationNodeEl(node) {
836
838
  el.title = node.html;
837
839
  return el;
838
840
  }
841
+ /** Create a single violation card with rule ID, help text, description, and affected nodes. */
842
+ function createViolationCard(violation, impactColor) {
843
+ const violationEl = document.createElement('div');
844
+ Object.assign(violationEl.style, {
845
+ marginBottom: '12px',
846
+ padding: '10px 12px',
847
+ backgroundColor: withAlpha(impactColor, 3),
848
+ border: `1px solid ${withAlpha(impactColor, 13)}`,
849
+ borderRadius: '6px',
850
+ });
851
+ // Rule ID
852
+ const ruleId = document.createElement('div');
853
+ Object.assign(ruleId.style, {
854
+ color: impactColor,
855
+ fontSize: '0.6875rem',
856
+ fontWeight: '600',
857
+ marginBottom: '4px',
858
+ });
859
+ ruleId.textContent = violation.id;
860
+ violationEl.appendChild(ruleId);
861
+ // Help text
862
+ const helpText = document.createElement('div');
863
+ Object.assign(helpText.style, {
864
+ color: CSS_COLORS.text,
865
+ fontSize: '0.75rem',
866
+ marginBottom: '4px',
867
+ });
868
+ helpText.textContent = violation.help;
869
+ violationEl.appendChild(helpText);
870
+ // Description
871
+ const desc = document.createElement('div');
872
+ Object.assign(desc.style, {
873
+ color: CSS_COLORS.textSecondary,
874
+ fontSize: '0.6875rem',
875
+ marginBottom: '6px',
876
+ });
877
+ desc.textContent = violation.description;
878
+ violationEl.appendChild(desc);
879
+ // Node count
880
+ const nodeCount = document.createElement('div');
881
+ Object.assign(nodeCount.style, {
882
+ color: CSS_COLORS.textMuted,
883
+ fontSize: '0.625rem',
884
+ marginBottom: '4px',
885
+ });
886
+ nodeCount.textContent = `${violation.nodes.length} element${violation.nodes.length === 1 ? '' : 's'} affected`;
887
+ violationEl.appendChild(nodeCount);
888
+ // Affected nodes (collapsed by default, show first 3)
889
+ const nodesPreview = document.createElement('div');
890
+ Object.assign(nodesPreview.style, {
891
+ marginTop: '6px',
892
+ });
893
+ const visibleNodes = violation.nodes.slice(0, 3);
894
+ for (const node of visibleNodes) {
895
+ nodesPreview.appendChild(createViolationNodeEl(node));
896
+ }
897
+ if (violation.nodes.length > 3) {
898
+ const moreBtn = document.createElement('button');
899
+ Object.assign(moreBtn.style, {
900
+ background: 'none',
901
+ border: 'none',
902
+ color: impactColor,
903
+ fontSize: '0.625rem',
904
+ cursor: 'pointer',
905
+ padding: '2px 0',
906
+ fontFamily: FONT_MONO,
907
+ });
908
+ moreBtn.textContent = `+ ${violation.nodes.length - 3} more`;
909
+ moreBtn.onclick = () => {
910
+ moreBtn.remove();
911
+ for (const node of violation.nodes.slice(3)) {
912
+ nodesPreview.appendChild(createViolationNodeEl(node));
913
+ }
914
+ };
915
+ nodesPreview.appendChild(moreBtn);
916
+ }
917
+ violationEl.appendChild(nodesPreview);
918
+ return violationEl;
919
+ }
839
920
  function renderA11yViolationGroup(container, impact, violations) {
840
921
  const impactColor = getImpactColor(impact);
841
922
  const section = document.createElement('div');
@@ -854,82 +935,7 @@ function renderA11yViolationGroup(container, impact, violations) {
854
935
  sectionTitle.textContent = `${impact} (${violations.length})`;
855
936
  section.appendChild(sectionTitle);
856
937
  for (const violation of violations) {
857
- const violationEl = document.createElement('div');
858
- Object.assign(violationEl.style, {
859
- marginBottom: '12px',
860
- padding: '10px 12px',
861
- backgroundColor: withAlpha(impactColor, 3),
862
- border: `1px solid ${withAlpha(impactColor, 13)}`,
863
- borderRadius: '6px',
864
- });
865
- // Rule ID
866
- const ruleId = document.createElement('div');
867
- Object.assign(ruleId.style, {
868
- color: impactColor,
869
- fontSize: '0.6875rem',
870
- fontWeight: '600',
871
- marginBottom: '4px',
872
- });
873
- ruleId.textContent = violation.id;
874
- violationEl.appendChild(ruleId);
875
- // Help text
876
- const helpText = document.createElement('div');
877
- Object.assign(helpText.style, {
878
- color: CSS_COLORS.text,
879
- fontSize: '0.75rem',
880
- marginBottom: '4px',
881
- });
882
- helpText.textContent = violation.help;
883
- violationEl.appendChild(helpText);
884
- // Description
885
- const desc = document.createElement('div');
886
- Object.assign(desc.style, {
887
- color: CSS_COLORS.textSecondary,
888
- fontSize: '0.6875rem',
889
- marginBottom: '6px',
890
- });
891
- desc.textContent = violation.description;
892
- violationEl.appendChild(desc);
893
- // Node count
894
- const nodeCount = document.createElement('div');
895
- Object.assign(nodeCount.style, {
896
- color: CSS_COLORS.textMuted,
897
- fontSize: '0.625rem',
898
- marginBottom: '4px',
899
- });
900
- nodeCount.textContent = `${violation.nodes.length} element${violation.nodes.length === 1 ? '' : 's'} affected`;
901
- violationEl.appendChild(nodeCount);
902
- // Affected nodes (collapsed by default, show first 3)
903
- const nodesPreview = document.createElement('div');
904
- Object.assign(nodesPreview.style, {
905
- marginTop: '6px',
906
- });
907
- const visibleNodes = violation.nodes.slice(0, 3);
908
- for (const node of visibleNodes) {
909
- nodesPreview.appendChild(createViolationNodeEl(node));
910
- }
911
- if (violation.nodes.length > 3) {
912
- const moreBtn = document.createElement('button');
913
- Object.assign(moreBtn.style, {
914
- background: 'none',
915
- border: 'none',
916
- color: impactColor,
917
- fontSize: '0.625rem',
918
- cursor: 'pointer',
919
- padding: '2px 0',
920
- fontFamily: FONT_MONO,
921
- });
922
- moreBtn.textContent = `+ ${violation.nodes.length - 3} more`;
923
- moreBtn.onclick = () => {
924
- moreBtn.remove();
925
- for (const node of violation.nodes.slice(3)) {
926
- nodesPreview.appendChild(createViolationNodeEl(node));
927
- }
928
- };
929
- nodesPreview.appendChild(moreBtn);
930
- }
931
- violationEl.appendChild(nodesPreview);
932
- section.appendChild(violationEl);
938
+ section.appendChild(createViolationCard(violation, impactColor));
933
939
  }
934
940
  container.appendChild(section);
935
941
  }