Änderungen von Dokument Prepare

Zuletzt geändert von Veronika Kocher am Freitag, 18 Sep. 2026 16:47

Von Version 1.7
bearbeitet von Veronika Kocher
am Donnerstag, 03 Sep. 2026 9:34
Änderungskommentar: Es gibt keinen Kommentar für diese Version
Auf Version 1.8
bearbeitet von Veronika Kocher
am Donnerstag, 03 Sep. 2026 10:09
Änderungskommentar: Es gibt keinen Kommentar für diese Version

Zusammenfassung

Details

Seiteneigenschaften
Inhalt
... ... @@ -483,15 +483,104 @@
483 483  </div>
484 484  {{/html}}
485 485  
486 -{{html clean="false"}}
486 +\n\n
487 +\n\n{{html clean="false"}}
487 487  <!-- SN_HORIZONTAL_BOX_HARMONIZER_START -->
488 -<style data-sn-horizontal-box-harmonizer="4">
489 -[data-sn-equal-height-managed],
490 -[data-sn-first-section-aligned]{
489 +
490 +<style data-sn-horizontal-box-harmonizer="7">
491 +/* ---------- Typography: never justify headings ---------- */
492 +#xwikicontent h1,
493 +#xwikicontent h2,
494 +#xwikicontent h3,
495 +#xwikicontent h4,
496 +#xwikicontent h5,
497 +#xwikicontent h6,
498 +#xwikicontent .box-title,
499 +#xwikicontent [role="heading"],
500 +#xwikicontent h1 a,
501 +#xwikicontent h2 a,
502 +#xwikicontent h3 a,
503 +#xwikicontent h4 a,
504 +#xwikicontent h5 a,
505 +#xwikicontent h6 a,
506 +#xwikicontent .box-title a,
507 +#xwikicontent [role="heading"] a{
508 + text-align:left!important;
509 + text-justify:auto!important;
510 + word-spacing:normal!important;
511 +}
512 +
513 +/* ---------- Native XWiki semantic callouts ---------- */
514 +#xwikicontent .box.infomessage,
515 +#xwikicontent .infomessage,
516 +#xwikicontent .box.warningmessage,
517 +#xwikicontent .warningmessage,
518 +#xwikicontent .box.successmessage,
519 +#xwikicontent .successmessage,
520 +#xwikicontent .box.errormessage,
521 +#xwikicontent .errormessage,
522 +#xwikicontent [data-sn-callout-harmonized="1"]{
523 + box-sizing:border-box!important;
524 + border-radius:8px!important;
525 + border-top:1px solid rgba(0,0,0,.08)!important;
526 + border-right:1px solid rgba(0,0,0,.08)!important;
527 + border-bottom:1px solid rgba(0,0,0,.08)!important;
528 + border-left:4px solid var(--sn-callout-accent,#21b6d7)!important;
529 + padding:14px 16px!important;
530 + text-align:left!important;
531 + text-justify:auto!important;
532 + word-spacing:normal!important;
533 + box-shadow:none!important;
534 +}
535 +
536 +#xwikicontent .box.infomessage,
537 +#xwikicontent .infomessage{
538 + --sn-callout-accent:#21b6d7;
539 + background:#eaf7fb!important;
540 +}
541 +
542 +#xwikicontent .box.warningmessage,
543 +#xwikicontent .warningmessage{
544 + --sn-callout-accent:#f0aa00;
545 + background:#fff8e7!important;
546 +}
547 +
548 +#xwikicontent .box.successmessage,
549 +#xwikicontent .successmessage{
550 + --sn-callout-accent:#38b76a;
551 + background:#edf9f1!important;
552 +}
553 +
554 +#xwikicontent .box.errormessage,
555 +#xwikicontent .errormessage{
556 + --sn-callout-accent:#e94b13;
557 + background:#fff1ec!important;
558 +}
559 +
560 +#xwikicontent [data-sn-callout-harmonized="1"] > :first-child,
561 +#xwikicontent .infomessage > :first-child,
562 +#xwikicontent .warningmessage > :first-child,
563 +#xwikicontent .successmessage > :first-child,
564 +#xwikicontent .errormessage > :first-child{
565 + margin-top:0!important;
566 +}
567 +
568 +#xwikicontent [data-sn-callout-harmonized="1"] > :last-child,
569 +#xwikicontent .infomessage > :last-child,
570 +#xwikicontent .warningmessage > :last-child,
571 +#xwikicontent .successmessage > :last-child,
572 +#xwikicontent .errormessage > :last-child{
573 + margin-bottom:0!important;
574 +}
575 +
576 +#xwikicontent [data-sn-equal-height-managed],
577 +#xwikicontent [data-sn-first-section-aligned],
578 +#xwikicontent [data-sn-first-content-aligned]{
491 491   box-sizing:border-box;
492 492  }
493 493  </style>
494 -<script data-sn-horizontal-box-harmonizer="4">
582 +
583 +<script data-sn-horizontal-box-harmonizer="7">
495 495  (function(){
496 496   "use strict";
497 497  
... ... @@ -499,8 +499,10 @@
499 499   var ROW_TOLERANCE=6;
500 500   var MIN_W=150;
501 501   var MIN_H=36;
502 - var MAX_PARENT_CHILDREN=16;
591 + var MAX_PARENT_CHILDREN=18;
503 503   var SEARCH_DEPTH=5;
593 + var RESIZE_DEBOUNCE=220;
594 + var MIN_WIDTH_CHANGE=2;
504 504  
505 505   var root=
506 506   document.getElementById("xwikicontent")||
... ... @@ -512,10 +512,16 @@
512 512  
513 513   var heightManaged=new Set();
514 514   var sectionManaged=new Set();
606 + var contentManaged=new Set();
607 +
515 515   var originalMinHeight=new WeakMap();
516 516   var originalMarginTop=new WeakMap();
517 - var raf=0;
518 518  
611 + var resizeTimer=0;
612 + var lastWidth=-1;
613 + var hasApplied=false;
614 + var applying=false;
615 +
519 519   function num(v){
520 520   var n=parseFloat(v);
521 521   return Number.isFinite(n)?n:0;
... ... @@ -542,6 +542,13 @@
542 542   );
543 543   }
544 544  
642 + function normalizeText(text){
643 + return String(text||"")
644 + .replace(/\s+/g," ")
645 + .trim()
646 + .toUpperCase();
647 + }
648 +
545 545   function excluded(el){
546 546   return (
547 547   !el.matches||
... ... @@ -551,57 +551,84 @@
551 551   );
552 552   }
553 553  
554 - function namedLikeBox(el){
658 + /* ---------- Custom coloured callouts ---------- */
659 +
660 + function nativeCallout(el){
555 555   var c=
556 556   typeof el.className==="string"
557 - ? el.className
663 + ? el.className.toLowerCase()
558 558   : "";
559 559  
560 - return /(^|[s_-])(box|card|tile|panel|role|check|service|source|call|area|detail|resource|topic|warn|info)([s_-]|$)/i.test(c);
666 + return (
667 + /(^|\s)(infomessage|warningmessage|successmessage|errormessage)(\s|$)/.test(c)
668 + );
561 561   }
562 562  
563 - function boxLike(el){
564 - if(!visible(el,MIN_W,MIN_H)||excluded(el))return false;
671 + function customCallout(el){
672 + if(!visible(el,220,42)||excluded(el))return false;
565 565  
566 - var s=getComputedStyle(el);
567 567   var r=el.getBoundingClientRect();
568 568  
569 - var border=
570 - num(s.borderTopWidth)+
571 - num(s.borderRightWidth)+
572 - num(s.borderBottomWidth)+
573 - num(s.borderLeftWidth);
676 + if(r.height>520)return false;
574 574  
575 - var radius=Math.max(
576 - num(s.borderTopLeftRadius),
577 - num(s.borderTopRightRadius),
578 - num(s.borderBottomLeftRadius),
579 - num(s.borderBottomRightRadius)
580 - );
678 + var s=getComputedStyle(el);
581 581  
582 582   var bg=
583 583   s.backgroundColor&&
584 584   s.backgroundColor!=="rgba(0, 0, 0, 0)"&&
585 - s.backgroundColor!=="transparent";
683 + s.backgroundColor!=="transparent"&&
684 + s.backgroundColor!=="rgb(255, 255, 255)";
586 586  
587 - var shadow=
588 - s.boxShadow&&
589 - s.boxShadow!=="none";
686 + var left=
687 + num(s.borderLeftWidth);
590 590  
591 591   return (
592 - namedLikeBox(el)||
593 - border>=1||
594 - (bg&&radius>=3)||
595 - (shadow&&r.width>=180)
690 + bg&&
691 + left>=2
596 596   );
597 597   }
598 598  
695 + function harmoniseCustomCallouts(){
696 + Array.from(
697 + root.querySelectorAll(
698 + "div,aside,section"
699 + )
700 + ).forEach(function(el){
701 + if(nativeCallout(el))return;
702 + if(!customCallout(el))return;
703 +
704 + var s=getComputedStyle(el);
705 +
706 + el.setAttribute(
707 + "data-sn-callout-harmonized",
708 + "1"
709 + );
710 +
711 + if(
712 + s.borderLeftColor
713 + ){
714 + el.style.setProperty(
715 + "--sn-callout-accent",
716 + s.borderLeftColor
717 + );
718 + }
719 + });
720 + }
721 +
722 + /* ---------- Restore only values V7 itself changed ---------- */
723 +
599 599   function saveOriginal(el,map,property){
600 600   if(map.has(el))return;
601 601  
602 602   map.set(el,{
603 - value:el.style.getPropertyValue(property),
604 - priority:el.style.getPropertyPriority(property)
728 + value:
729 + el.style.getPropertyValue(
730 + property
731 + ),
732 + priority:
733 + el.style.getPropertyPriority(
734 + property
735 + )
605 605   });
606 606   }
607 607  
... ... @@ -615,11 +615,13 @@
615 615   original.priority||""
616 616   );
617 617   }else{
618 - el.style.removeProperty(property);
749 + el.style.removeProperty(
750 + property
751 + );
619 619   }
620 620   }
621 621  
622 - function reset(){
755 + function resetRuntime(){
623 623   heightManaged.forEach(function(el){
624 624   restoreOriginal(
625 625   el,
... ... @@ -644,16 +644,79 @@
644 644   );
645 645   });
646 646  
780 + contentManaged.forEach(function(el){
781 + restoreOriginal(
782 + el,
783 + originalMarginTop,
784 + "margin-top"
785 + );
786 +
787 + el.removeAttribute(
788 + "data-sn-first-content-aligned"
789 + );
790 + });
791 +
647 647   heightManaged.clear();
648 648   sectionManaged.clear();
794 + contentManaged.clear();
649 649   }
650 650  
797 + /* ---------- Card detection ---------- */
798 +
799 + function namedLikeBox(el){
800 + var c=
801 + typeof el.className==="string"
802 + ? el.className
803 + : "";
804 +
805 + return /(^|[\s_-])(box|card|tile|panel|role|check|service|source|call|area|detail|resource|topic|warn|info)([\s_-]|$)/i.test(c);
806 + }
807 +
808 + function boxLike(el){
809 + if(
810 + !visible(el,MIN_W,MIN_H)||
811 + excluded(el)
812 + ){
813 + return false;
814 + }
815 +
816 + var s=getComputedStyle(el);
817 + var r=el.getBoundingClientRect();
818 +
819 + var border=
820 + num(s.borderTopWidth)+
821 + num(s.borderRightWidth)+
822 + num(s.borderBottomWidth)+
823 + num(s.borderLeftWidth);
824 +
825 + var radius=Math.max(
826 + num(s.borderTopLeftRadius),
827 + num(s.borderTopRightRadius),
828 + num(s.borderBottomLeftRadius),
829 + num(s.borderBottomRightRadius)
830 + );
831 +
832 + var bg=
833 + s.backgroundColor&&
834 + s.backgroundColor!=="rgba(0, 0, 0, 0)"&&
835 + s.backgroundColor!=="transparent";
836 +
837 + var shadow=
838 + s.boxShadow&&
839 + s.boxShadow!=="none";
840 +
841 + return (
842 + namedLikeBox(el)||
843 + border>=1||
844 + (bg&&radius>=3)||
845 + (shadow&&r.width>=180)
846 + );
847 + }
848 +
651 651   function pickTarget(child){
652 652   if(!visible(child,MIN_W,MIN_H))return null;
653 653  
654 - if(boxLike(child)){
655 - return child;
656 - }
852 + if(boxLike(child))return child;
657 657  
658 658   var cr=child.getBoundingClientRect();
659 659   var q=[];
... ... @@ -715,7 +715,7 @@
715 715   return best;
716 716   }
717 717  
718 - function groupsByVisualRow(entries){
914 + function groupRows(entries){
719 719   var groups=[];
720 720  
721 721   entries
... ... @@ -755,7 +755,7 @@
755 755   return groups;
756 756   }
757 757  
758 - function validHorizontalGroup(group){
954 + function validRow(group){
759 759   if(group.entries.length<2)return false;
760 760  
761 761   var lefts=
... ... @@ -778,25 +778,26 @@
778 778  
779 779   if(widths.length<2)return false;
780 780  
781 - var minWidth=Math.min.apply(Math,widths);
782 - var maxWidth=Math.max.apply(Math,widths);
977 + var minW=
978 + Math.min.apply(Math,widths);
783 783  
980 + var maxW=
981 + Math.max.apply(Math,widths);
982 +
784 784   return (
785 - maxWidth/
786 - Math.max(1,minWidth)<=
984 + maxW/
985 + Math.max(1,minW)<=
787 787   1.9
788 788   );
789 789   }
790 790  
791 - function normalizeText(text){
792 - return String(text||"")
793 - .replace(/s+/g," ")
794 - .trim()
795 - .toUpperCase();
796 - }
990 + /* ---------- Known ARSN first section bars ---------- */
797 797  
798 - function exactKnownFirstSection(el){
799 - var text=normalizeText(el.textContent);
992 + function knownBar(el){
993 + var text=
994 + normalizeText(
995 + el.textContent
996 + );
800 800  
801 801   return (
802 802   text==="HIER STARTEN"||
... ... @@ -810,150 +810,274 @@
810 810   );
811 811   }
812 812  
813 - function visuallyFirstSectionBar(card){
814 - var cardRect=card.getBoundingClientRect();
815 - var candidates=[];
1010 + function findKnownBar(card){
1011 + var cardRect=
1012 + card.getBoundingClientRect();
816 816  
817 - Array.from(
818 - card.querySelectorAll(
819 - "div,p,span,strong,h3,h4,h5,h6"
1014 + var rows=
1015 + Array.from(
1016 + card.querySelectorAll(
1017 + "div,p,span,strong,h3,h4,h5,h6"
1018 + )
820 820   )
821 - ).forEach(function(el){
822 - if(!visible(el,20,6))return;
823 - if(excluded(el))return;
1020 + .filter(function(el){
1021 + if(!visible(el,20,6))return false;
1022 + if(!knownBar(el))return false;
824 824  
825 - var r=el.getBoundingClientRect();
826 - var relativeTop=r.top-cardRect.top;
1024 + var r=el.getBoundingClientRect();
827 827  
828 - if(
829 - relativeTop<45||
830 - relativeTop>cardRect.height*.72
831 - ){
832 - return;
833 - }
1026 + var rel=
1027 + r.top-
1028 + cardRect.top;
834 834  
835 - var widthRatio=
836 - r.width/
837 - Math.max(1,cardRect.width);
1030 + return (
1031 + rel>=35&&
1032 + rel<=
1033 + cardRect.height*.75
1034 + );
1035 + })
1036 + .sort(function(a,b){
1037 + return (
1038 + a.getBoundingClientRect().top-
1039 + b.getBoundingClientRect().top
1040 + );
1041 + });
838 838  
839 - if(
840 - widthRatio<.55||
841 - widthRatio>1.05||
842 - r.height>54
843 - ){
844 - return;
845 - }
1043 + return rows[0]||null;
1044 + }
846 846  
847 - var text=normalizeText(el.textContent);
1046 + function alignKnownBars(cards){
1047 + var rows=
1048 + cards.map(function(card){
1049 + var bar=
1050 + findKnownBar(card);
848 848  
849 - if(
850 - text.length<2||
851 - text.length>90
852 - ){
853 - return;
854 - }
1052 + if(!bar)return null;
855 855  
856 - var style=getComputedStyle(el);
1054 + var cr=
1055 + card.getBoundingClientRect();
857 857  
858 - var bg=
859 - style.backgroundColor&&
860 - style.backgroundColor!=="rgba(0, 0, 0, 0)"&&
861 - style.backgroundColor!=="transparent";
1057 + var br=
1058 + bar.getBoundingClientRect();
862 862  
863 - var border=
864 - num(style.borderTopWidth)+
865 - num(style.borderRightWidth)+
866 - num(style.borderBottomWidth)+
867 - num(style.borderLeftWidth);
1060 + return {
1061 + bar:bar,
1062 + relativeTop:
1063 + br.top-
1064 + cr.top
1065 + };
1066 + });
868 868  
869 - var mostlyUpper=
870 - text.length>=3&&
871 - text===text.toUpperCase();
1068 + if(
1069 + rows.some(function(row){
1070 + return !row;
1071 + })
1072 + ){
1073 + return false;
1074 + }
872 872  
873 - var exact=exactKnownFirstSection(el);
1076 + var target=
1077 + Math.max.apply(
1078 + Math,
1079 + rows.map(function(row){
1080 + return row.relativeTop;
1081 + })
1082 + );
874 874  
875 - var score=
876 - (exact?20:0)+
877 - (bg?6:0)+
878 - (border>=1?2:0)+
879 - (mostlyUpper?4:0)+
880 - (num(style.fontWeight)>=600?1:0);
1084 + rows.forEach(function(row){
1085 + var delta=
1086 + target-
1087 + row.relativeTop;
881 881  
882 - if(score>=7){
883 - candidates.push({
884 - el:el,
885 - relativeTop:relativeTop,
886 - width:r.width,
887 - score:score
888 - });
889 - }
890 - });
1089 + if(delta<=1)return;
891 891  
892 - if(!candidates.length)return null;
1091 + saveOriginal(
1092 + row.bar,
1093 + originalMarginTop,
1094 + "margin-top"
1095 + );
893 893  
894 - candidates.sort(function(a,b){
895 - if(a.score!==b.score)return b.score-a.score;
896 - if(Math.abs(a.relativeTop-b.relativeTop)>3){
897 - return a.relativeTop-b.relativeTop;
898 - }
899 - return b.width-a.width;
900 - });
1097 + var current=
1098 + num(
1099 + getComputedStyle(
1100 + row.bar
1101 + ).marginTop
1102 + );
901 901  
902 - // For exact known labels always prefer the earliest exact match.
903 - var exacts=candidates.filter(function(c){
904 - return exactKnownFirstSection(c.el);
1104 + row.bar.style.setProperty(
1105 + "margin-top",
1106 + (
1107 + current+
1108 + delta
1109 + )+"px"
1110 + );
1111 +
1112 + row.bar.setAttribute(
1113 + "data-sn-first-section-aligned",
1114 + "7"
1115 + );
1116 +
1117 + sectionManaged.add(
1118 + row.bar
1119 + );
905 905   });
906 906  
907 - if(exacts.length){
908 - exacts.sort(function(a,b){
909 - return a.relativeTop-b.relativeTop||b.width-a.width;
1122 + return true;
1123 + }
1124 +
1125 + /* ---------- Generic first content after heading ---------- */
1126 +
1127 + function heading(card){
1128 + var cr=
1129 + card.getBoundingClientRect();
1130 +
1131 + var rows=
1132 + Array.from(
1133 + card.querySelectorAll(
1134 + "h1,h2,h3,h4,h5,h6,.box-title,[role=heading]"
1135 + )
1136 + )
1137 + .filter(function(el){
1138 + if(!visible(el,20,8))return false;
1139 +
1140 + var r=el.getBoundingClientRect();
1141 +
1142 + var rel=
1143 + r.top-
1144 + cr.top;
1145 +
1146 + return (
1147 + rel>=0&&
1148 + rel<=
1149 + Math.min(
1150 + 160,
1151 + cr.height*.45
1152 + )
1153 + );
1154 + })
1155 + .sort(function(a,b){
1156 + return (
1157 + a.getBoundingClientRect().top-
1158 + b.getBoundingClientRect().top
1159 + );
910 910   });
911 - return exacts[0].el;
912 - }
913 913  
914 - // Generic fallback: earliest high-confidence bar.
915 - candidates.sort(function(a,b){
916 - return a.relativeTop-b.relativeTop||b.score-a.score||b.width-a.width;
917 - });
1162 + return rows[0]||null;
1163 + }
918 918  
919 - return candidates[0].el;
1165 + function firstContent(card,h){
1166 + if(!h)return null;
1167 +
1168 + var cr=
1169 + card.getBoundingClientRect();
1170 +
1171 + var hr=
1172 + h.getBoundingClientRect();
1173 +
1174 + var rows=
1175 + Array.from(
1176 + card.querySelectorAll(
1177 + "p,div,ul,ol,dl,blockquote"
1178 + )
1179 + )
1180 + .filter(function(el){
1181 + if(
1182 + el===h||
1183 + h.contains(el)||
1184 + el.contains(h)
1185 + ){
1186 + return false;
1187 + }
1188 +
1189 + if(!visible(el,30,8))return false;
1190 +
1191 + var text=
1192 + String(el.textContent||"")
1193 + .replace(/\s+/g," ")
1194 + .trim();
1195 +
1196 + if(text.length<3)return false;
1197 +
1198 + var r=el.getBoundingClientRect();
1199 +
1200 + if(
1201 + r.top<
1202 + hr.bottom-1
1203 + ){
1204 + return false;
1205 + }
1206 +
1207 + if(
1208 + r.top-cr.top>
1209 + Math.min(
1210 + 280,
1211 + cr.height*.65
1212 + )
1213 + ){
1214 + return false;
1215 + }
1216 +
1217 + if(
1218 + r.height>
1219 + cr.height*.58
1220 + ){
1221 + return false;
1222 + }
1223 +
1224 + return true;
1225 + })
1226 + .sort(function(a,b){
1227 + var ar=
1228 + a.getBoundingClientRect();
1229 +
1230 + var br=
1231 + b.getBoundingClientRect();
1232 +
1233 + return (
1234 + ar.top-br.top||
1235 + ar.height-br.height
1236 + );
1237 + });
1238 +
1239 + return rows[0]||null;
920 920   }
921 921  
922 - function alignFirstSections(cards){
1242 + function alignGeneric(cards){
923 923   var rows=
924 924   cards.map(function(card){
925 - var bar=
926 - visuallyFirstSectionBar(card);
1245 + var h=
1246 + heading(card);
927 927  
928 - if(!bar)return null;
1248 + var c=
1249 + firstContent(
1250 + card,
1251 + h
1252 + );
929 929  
930 - var cardRect=
1254 + if(!h||!c)return null;
1255 +
1256 + var cr=
931 931   card.getBoundingClientRect();
932 932  
933 - var barRect=
934 - bar.getBoundingClientRect();
1259 + var rr=
1260 + c.getBoundingClientRect();
935 935  
936 936   return {
937 - card:card,
938 - bar:bar,
1263 + content:c,
939 939   relativeTop:
940 - barRect.top-cardRect.top,
941 - label:
942 - normalizeText(bar.textContent)
1265 + rr.top-cr.top
943 943   };
944 944   });
945 945  
946 - // Conservative: only align when every card in the horizontal row
947 - // has a reliable first section bar.
948 948   if(
949 949   rows.some(function(row){
950 950   return !row;
951 951   })
952 952   ){
953 - return null;
1274 + return false;
954 954   }
955 955  
956 - var targetTop=
1277 + var target=
957 957   Math.max.apply(
958 958   Math,
959 959   rows.map(function(row){
... ... @@ -963,54 +963,47 @@
963 963  
964 964   rows.forEach(function(row){
965 965   var delta=
966 - targetTop-
1287 + target-
967 967   row.relativeTop;
968 968  
969 969   if(delta<=1)return;
970 970  
971 971   saveOriginal(
972 - row.bar,
1293 + row.content,
973 973   originalMarginTop,
974 974   "margin-top"
975 975   );
976 976  
977 - var currentMargin=
1298 + var current=
978 978   num(
979 979   getComputedStyle(
980 - row.bar
1301 + row.content
981 981   ).marginTop
982 982   );
983 983  
984 - row.bar.style.setProperty(
1305 + row.content.style.setProperty(
985 985   "margin-top",
986 986   (
987 - currentMargin+
1308 + current+
988 988   delta
989 989   )+"px"
990 990   );
991 991  
992 - row.bar.setAttribute(
993 - "data-sn-first-section-aligned",
994 - "4"
1313 + row.content.setAttribute(
1314 + "data-sn-first-content-aligned",
1315 + "7"
995 995   );
996 996  
997 - sectionManaged.add(row.bar);
1318 + contentManaged.add(
1319 + row.content
1320 + );
998 998   });
999 999  
1000 - return {
1001 - labels:
1002 - rows.map(function(row){
1003 - return row.label;
1004 - }),
1005 - targetRelativeTop:
1006 - Math.round(targetTop)
1007 - };
1323 + return true;
1008 1008   }
1009 1009  
1010 - function applyGroup(group){
1011 - if(!validHorizontalGroup(group)){
1012 - return null;
1013 - }
1326 + function applyRow(group){
1327 + if(!validRow(group))return false;
1014 1014  
1015 1015   var cards=[];
1016 1016   var seen=new Set();
... ... @@ -1022,25 +1022,30 @@
1022 1022   }
1023 1023   });
1024 1024  
1025 - if(cards.length<2)return null;
1339 + if(cards.length<2)return false;
1026 1026  
1027 - var firstSection=
1028 - alignFirstSections(cards);
1341 + if(
1342 + !alignKnownBars(cards)
1343 + ){
1344 + alignGeneric(cards);
1345 + }
1029 1029  
1030 - // The section alignment can change natural card heights.
1031 - // Measure only afterwards.
1032 1032   var maxHeight=0;
1033 1033  
1034 1034   cards.forEach(function(card){
1035 - maxHeight=Math.max(
1036 - maxHeight,
1037 - card.getBoundingClientRect().height
1038 - );
1350 + maxHeight=
1351 + Math.max(
1352 + maxHeight,
1353 + card.getBoundingClientRect().height
1354 + );
1039 1039   });
1040 1040  
1041 - maxHeight=Math.ceil(maxHeight);
1357 + maxHeight=
1358 + Math.ceil(
1359 + maxHeight
1360 + );
1042 1042  
1043 - if(maxHeight<MIN_H)return null;
1362 + if(maxHeight<MIN_H)return false;
1044 1044  
1045 1045   cards.forEach(function(card){
1046 1046   saveOriginal(
... ... @@ -1056,125 +1056,228 @@
1056 1056  
1057 1057   card.setAttribute(
1058 1058   "data-sn-equal-height-managed",
1059 - "4"
1378 + "7"
1060 1060   );
1061 1061  
1062 1062   heightManaged.add(card);
1063 1063   });
1064 1064  
1065 - return {
1066 - cards:cards.length,
1067 - height:maxHeight,
1068 - firstSection:firstSection
1069 - };
1384 + return true;
1070 1070   }
1071 1071  
1072 - function run(){
1073 - reset();
1387 + function calculate(force){
1388 + if(applying)return;
1074 1074  
1390 + var width=
1391 + Math.round(
1392 + window.innerWidth
1393 + );
1394 +
1395 + var meaningful=
1396 + lastWidth<0||
1397 + Math.abs(
1398 + width-
1399 + lastWidth
1400 + )>=
1401 + MIN_WIDTH_CHANGE;
1402 +
1075 1075   if(
1076 - window.innerWidth<=MOBILE_MAX
1404 + hasApplied&&
1405 + !force&&
1406 + !meaningful
1077 1077   ){
1078 1078   return;
1079 1079   }
1080 1080  
1081 - var parents=
1082 - Array.from(
1083 - root.querySelectorAll("*")
1084 - ).filter(function(parent){
1085 - return (
1086 - visible(parent,MIN_W,MIN_H)&&
1087 - parent.children&&
1088 - parent.children.length>=2&&
1089 - parent.children.length<=MAX_PARENT_CHILDREN
1090 - );
1091 - });
1411 + applying=true;
1092 1092  
1093 - var touched=new Set();
1413 + try{
1414 + harmoniseCustomCallouts();
1094 1094  
1095 - parents.forEach(function(parent){
1096 - var entries=[];
1416 + if(width<=MOBILE_MAX){
1417 + if(hasApplied){
1418 + resetRuntime();
1419 + }
1097 1097  
1098 - Array.from(parent.children).forEach(function(child){
1099 - if(!visible(child,MIN_W,MIN_H))return;
1421 + hasApplied=false;
1422 + lastWidth=width;
1423 + return;
1424 + }
1100 1100  
1101 - var target=pickTarget(child);
1102 - if(!target)return;
1426 + if(hasApplied){
1427 + resetRuntime();
1428 + }
1103 1103  
1104 - entries.push({
1105 - target:target,
1106 - rect:target.getBoundingClientRect()
1430 + var parents=
1431 + Array.from(
1432 + root.querySelectorAll("*")
1433 + )
1434 + .filter(function(parent){
1435 + return (
1436 + visible(
1437 + parent,
1438 + MIN_W,
1439 + MIN_H
1440 + )&&
1441 + parent.children&&
1442 + parent.children.length>=2&&
1443 + parent.children.length<=
1444 + MAX_PARENT_CHILDREN
1445 + );
1107 1107   });
1108 - });
1109 1109  
1110 - if(entries.length<2)return;
1448 + var touched=
1449 + new Set();
1111 1111  
1112 - groupsByVisualRow(entries).forEach(function(group){
1113 - if(!validHorizontalGroup(group))return;
1451 + parents.forEach(function(parent){
1452 + var entries=[];
1114 1114  
1115 - var signature=
1116 - group.entries.map(function(entry){
1117 - return entry.target;
1118 - });
1454 + Array.from(
1455 + parent.children
1456 + ).forEach(function(child){
1457 + if(
1458 + !visible(
1459 + child,
1460 + MIN_W,
1461 + MIN_H
1462 + )
1463 + ){
1464 + return;
1465 + }
1119 1119  
1120 - if(
1121 - !signature.some(function(el){
1122 - return !touched.has(el);
1123 - })
1124 - ){
1125 - return;
1126 - }
1467 + var target=
1468 + pickTarget(child);
1127 1127  
1128 - var applied=
1129 - applyGroup(group);
1470 + if(!target)return;
1130 1130  
1131 - if(applied){
1132 - signature.forEach(function(el){
1133 - touched.add(el);
1472 + entries.push({
1473 + target:target,
1474 + rect:
1475 + target.getBoundingClientRect()
1134 1134   });
1135 - }
1477 + });
1478 +
1479 + groupRows(
1480 + entries
1481 + ).forEach(function(group){
1482 + if(!validRow(group))return;
1483 +
1484 + var targets=
1485 + group.entries.map(function(entry){
1486 + return entry.target;
1487 + });
1488 +
1489 + if(
1490 + !targets.some(function(el){
1491 + return !touched.has(el);
1492 + })
1493 + ){
1494 + return;
1495 + }
1496 +
1497 + if(applyRow(group)){
1498 + targets.forEach(function(el){
1499 + touched.add(el);
1500 + });
1501 + }
1502 + });
1136 1136   });
1137 - });
1504 +
1505 + lastWidth=width;
1506 + hasApplied=true;
1507 + }finally{
1508 + applying=false;
1509 + }
1138 1138   }
1139 1139  
1140 - function schedule(){
1141 - if(raf)cancelAnimationFrame(raf);
1512 + function resize(){
1513 + if(resizeTimer){
1514 + clearTimeout(
1515 + resizeTimer
1516 + );
1517 + }
1142 1142  
1143 - raf=requestAnimationFrame(function(){
1144 - raf=requestAnimationFrame(function(){
1145 - raf=0;
1146 - run();
1147 - });
1148 - });
1519 + resizeTimer=
1520 + setTimeout(
1521 + function(){
1522 + resizeTimer=0;
1523 + calculate(false);
1524 + },
1525 + RESIZE_DEBOUNCE
1526 + );
1149 1149   }
1150 1150  
1151 - addEventListener("resize",schedule,{passive:true});
1152 - addEventListener("load",schedule,{once:true});
1529 + function waitImages(){
1530 + var images=
1531 + Array.from(
1532 + root.querySelectorAll("img")
1533 + );
1153 1153  
1154 - root.addEventListener(
1155 - "click",
1156 - function(){
1157 - setTimeout(schedule,0);
1158 - },
1159 - true
1160 - );
1535 + return Promise.all(
1536 + images.map(function(img){
1537 + if(img.complete){
1538 + return Promise.resolve();
1539 + }
1161 1161  
1162 - Array.from(root.querySelectorAll("img")).forEach(function(img){
1163 - if(!img.complete){
1164 - img.addEventListener("load",schedule,{once:true});
1541 + return new Promise(function(resolve){
1542 + var done=function(){
1543 + resolve();
1544 + };
1545 +
1546 + img.addEventListener(
1547 + "load",
1548 + done,
1549 + {once:true}
1550 + );
1551 +
1552 + img.addEventListener(
1553 + "error",
1554 + done,
1555 + {once:true}
1556 + );
1557 + });
1558 + })
1559 + );
1560 + }
1561 +
1562 + async function initial(){
1563 + if(
1564 + document.readyState!==
1565 + "complete"
1566 + ){
1567 + await new Promise(function(resolve){
1568 + addEventListener(
1569 + "load",
1570 + resolve,
1571 + {once:true}
1572 + );
1573 + });
1165 1165   }
1166 - });
1167 1167  
1168 - if(document.fonts&&document.fonts.ready){
1169 - document.fonts.ready.then(schedule);
1576 + if(
1577 + document.fonts&&
1578 + document.fonts.ready
1579 + ){
1580 + try{
1581 + await document.fonts.ready;
1582 + }catch(error){}
1583 + }
1584 +
1585 + await waitImages();
1586 +
1587 + // Exactly one initial layout calculation.
1588 + calculate(true);
1170 1170   }
1171 1171  
1172 - schedule();
1173 - setTimeout(schedule,200);
1174 - setTimeout(schedule,650);
1175 - setTimeout(schedule,1400);
1591 + addEventListener(
1592 + "resize",
1593 + resize,
1594 + {passive:true}
1595 + );
1596 +
1597 + initial();
1176 1176  })();
1177 1177  </script>
1178 -<!-- SN_HORIZONTAL_BOX_HARMONIZER_END -->
1179 -{{/html}}
1180 1180  
1601 +<!-- SN_HORIZONTAL_BOX_HARMONIZER_END -->
1602 +{{/html}}\n