Last modified by Veronika Kocher on Friday, 18 Sep 2026 16:47

From version 39.22
edited by Veronika Kocher
on Thursday, 03 Sep 2026 9:34
Change comment: There is no comment for this version
To version 39.23
edited by Veronika Kocher
on Thursday, 03 Sep 2026 10:09
Change comment: There is no comment for this version

Summary

Details

Page properties
Content
... ... @@ -639,15 +639,104 @@
639 639  {{/info}}
640 640  )))
641 641  
642 -{{html clean="false"}}
642 +\n\n
643 +\n\n{{html clean="false"}}
643 643  <!-- SN_HORIZONTAL_BOX_HARMONIZER_START -->
644 -<style data-sn-horizontal-box-harmonizer="4">
645 -[data-sn-equal-height-managed],
646 -[data-sn-first-section-aligned]{
645 +
646 +<style data-sn-horizontal-box-harmonizer="7">
647 +/* ---------- Typography: never justify headings ---------- */
648 +#xwikicontent h1,
649 +#xwikicontent h2,
650 +#xwikicontent h3,
651 +#xwikicontent h4,
652 +#xwikicontent h5,
653 +#xwikicontent h6,
654 +#xwikicontent .box-title,
655 +#xwikicontent [role="heading"],
656 +#xwikicontent h1 a,
657 +#xwikicontent h2 a,
658 +#xwikicontent h3 a,
659 +#xwikicontent h4 a,
660 +#xwikicontent h5 a,
661 +#xwikicontent h6 a,
662 +#xwikicontent .box-title a,
663 +#xwikicontent [role="heading"] a{
664 + text-align:left!important;
665 + text-justify:auto!important;
666 + word-spacing:normal!important;
667 +}
668 +
669 +/* ---------- Native XWiki semantic callouts ---------- */
670 +#xwikicontent .box.infomessage,
671 +#xwikicontent .infomessage,
672 +#xwikicontent .box.warningmessage,
673 +#xwikicontent .warningmessage,
674 +#xwikicontent .box.successmessage,
675 +#xwikicontent .successmessage,
676 +#xwikicontent .box.errormessage,
677 +#xwikicontent .errormessage,
678 +#xwikicontent [data-sn-callout-harmonized="1"]{
679 + box-sizing:border-box!important;
680 + border-radius:8px!important;
681 + border-top:1px solid rgba(0,0,0,.08)!important;
682 + border-right:1px solid rgba(0,0,0,.08)!important;
683 + border-bottom:1px solid rgba(0,0,0,.08)!important;
684 + border-left:4px solid var(--sn-callout-accent,#21b6d7)!important;
685 + padding:14px 16px!important;
686 + text-align:left!important;
687 + text-justify:auto!important;
688 + word-spacing:normal!important;
689 + box-shadow:none!important;
690 +}
691 +
692 +#xwikicontent .box.infomessage,
693 +#xwikicontent .infomessage{
694 + --sn-callout-accent:#21b6d7;
695 + background:#eaf7fb!important;
696 +}
697 +
698 +#xwikicontent .box.warningmessage,
699 +#xwikicontent .warningmessage{
700 + --sn-callout-accent:#f0aa00;
701 + background:#fff8e7!important;
702 +}
703 +
704 +#xwikicontent .box.successmessage,
705 +#xwikicontent .successmessage{
706 + --sn-callout-accent:#38b76a;
707 + background:#edf9f1!important;
708 +}
709 +
710 +#xwikicontent .box.errormessage,
711 +#xwikicontent .errormessage{
712 + --sn-callout-accent:#e94b13;
713 + background:#fff1ec!important;
714 +}
715 +
716 +#xwikicontent [data-sn-callout-harmonized="1"] > :first-child,
717 +#xwikicontent .infomessage > :first-child,
718 +#xwikicontent .warningmessage > :first-child,
719 +#xwikicontent .successmessage > :first-child,
720 +#xwikicontent .errormessage > :first-child{
721 + margin-top:0!important;
722 +}
723 +
724 +#xwikicontent [data-sn-callout-harmonized="1"] > :last-child,
725 +#xwikicontent .infomessage > :last-child,
726 +#xwikicontent .warningmessage > :last-child,
727 +#xwikicontent .successmessage > :last-child,
728 +#xwikicontent .errormessage > :last-child{
729 + margin-bottom:0!important;
730 +}
731 +
732 +#xwikicontent [data-sn-equal-height-managed],
733 +#xwikicontent [data-sn-first-section-aligned],
734 +#xwikicontent [data-sn-first-content-aligned]{
647 647   box-sizing:border-box;
648 648  }
649 649  </style>
650 -<script data-sn-horizontal-box-harmonizer="4">
738 +
739 +<script data-sn-horizontal-box-harmonizer="7">
651 651  (function(){
652 652   "use strict";
653 653  
... ... @@ -655,8 +655,10 @@
655 655   var ROW_TOLERANCE=6;
656 656   var MIN_W=150;
657 657   var MIN_H=36;
658 - var MAX_PARENT_CHILDREN=16;
747 + var MAX_PARENT_CHILDREN=18;
659 659   var SEARCH_DEPTH=5;
749 + var RESIZE_DEBOUNCE=220;
750 + var MIN_WIDTH_CHANGE=2;
660 660  
661 661   var root=
662 662   document.getElementById("xwikicontent")||
... ... @@ -668,10 +668,16 @@
668 668  
669 669   var heightManaged=new Set();
670 670   var sectionManaged=new Set();
762 + var contentManaged=new Set();
763 +
671 671   var originalMinHeight=new WeakMap();
672 672   var originalMarginTop=new WeakMap();
673 - var raf=0;
674 674  
767 + var resizeTimer=0;
768 + var lastWidth=-1;
769 + var hasApplied=false;
770 + var applying=false;
771 +
675 675   function num(v){
676 676   var n=parseFloat(v);
677 677   return Number.isFinite(n)?n:0;
... ... @@ -698,6 +698,13 @@
698 698   );
699 699   }
700 700  
798 + function normalizeText(text){
799 + return String(text||"")
800 + .replace(/\s+/g," ")
801 + .trim()
802 + .toUpperCase();
803 + }
804 +
701 701   function excluded(el){
702 702   return (
703 703   !el.matches||
... ... @@ -707,57 +707,84 @@
707 707   );
708 708   }
709 709  
710 - function namedLikeBox(el){
814 + /* ---------- Custom coloured callouts ---------- */
815 +
816 + function nativeCallout(el){
711 711   var c=
712 712   typeof el.className==="string"
713 - ? el.className
819 + ? el.className.toLowerCase()
714 714   : "";
715 715  
716 - return /(^|[s_-])(box|card|tile|panel|role|check|service|source|call|area|detail|resource|topic|warn|info)([s_-]|$)/i.test(c);
822 + return (
823 + /(^|\s)(infomessage|warningmessage|successmessage|errormessage)(\s|$)/.test(c)
824 + );
717 717   }
718 718  
719 - function boxLike(el){
720 - if(!visible(el,MIN_W,MIN_H)||excluded(el))return false;
827 + function customCallout(el){
828 + if(!visible(el,220,42)||excluded(el))return false;
721 721  
722 - var s=getComputedStyle(el);
723 723   var r=el.getBoundingClientRect();
724 724  
725 - var border=
726 - num(s.borderTopWidth)+
727 - num(s.borderRightWidth)+
728 - num(s.borderBottomWidth)+
729 - num(s.borderLeftWidth);
832 + if(r.height>520)return false;
730 730  
731 - var radius=Math.max(
732 - num(s.borderTopLeftRadius),
733 - num(s.borderTopRightRadius),
734 - num(s.borderBottomLeftRadius),
735 - num(s.borderBottomRightRadius)
736 - );
834 + var s=getComputedStyle(el);
737 737  
738 738   var bg=
739 739   s.backgroundColor&&
740 740   s.backgroundColor!=="rgba(0, 0, 0, 0)"&&
741 - s.backgroundColor!=="transparent";
839 + s.backgroundColor!=="transparent"&&
840 + s.backgroundColor!=="rgb(255, 255, 255)";
742 742  
743 - var shadow=
744 - s.boxShadow&&
745 - s.boxShadow!=="none";
842 + var left=
843 + num(s.borderLeftWidth);
746 746  
747 747   return (
748 - namedLikeBox(el)||
749 - border>=1||
750 - (bg&&radius>=3)||
751 - (shadow&&r.width>=180)
846 + bg&&
847 + left>=2
752 752   );
753 753   }
754 754  
851 + function harmoniseCustomCallouts(){
852 + Array.from(
853 + root.querySelectorAll(
854 + "div,aside,section"
855 + )
856 + ).forEach(function(el){
857 + if(nativeCallout(el))return;
858 + if(!customCallout(el))return;
859 +
860 + var s=getComputedStyle(el);
861 +
862 + el.setAttribute(
863 + "data-sn-callout-harmonized",
864 + "1"
865 + );
866 +
867 + if(
868 + s.borderLeftColor
869 + ){
870 + el.style.setProperty(
871 + "--sn-callout-accent",
872 + s.borderLeftColor
873 + );
874 + }
875 + });
876 + }
877 +
878 + /* ---------- Restore only values V7 itself changed ---------- */
879 +
755 755   function saveOriginal(el,map,property){
756 756   if(map.has(el))return;
757 757  
758 758   map.set(el,{
759 - value:el.style.getPropertyValue(property),
760 - priority:el.style.getPropertyPriority(property)
884 + value:
885 + el.style.getPropertyValue(
886 + property
887 + ),
888 + priority:
889 + el.style.getPropertyPriority(
890 + property
891 + )
761 761   });
762 762   }
763 763  
... ... @@ -771,11 +771,13 @@
771 771   original.priority||""
772 772   );
773 773   }else{
774 - el.style.removeProperty(property);
905 + el.style.removeProperty(
906 + property
907 + );
775 775   }
776 776   }
777 777  
778 - function reset(){
911 + function resetRuntime(){
779 779   heightManaged.forEach(function(el){
780 780   restoreOriginal(
781 781   el,
... ... @@ -800,16 +800,79 @@
800 800   );
801 801   });
802 802  
936 + contentManaged.forEach(function(el){
937 + restoreOriginal(
938 + el,
939 + originalMarginTop,
940 + "margin-top"
941 + );
942 +
943 + el.removeAttribute(
944 + "data-sn-first-content-aligned"
945 + );
946 + });
947 +
803 803   heightManaged.clear();
804 804   sectionManaged.clear();
950 + contentManaged.clear();
805 805   }
806 806  
953 + /* ---------- Card detection ---------- */
954 +
955 + function namedLikeBox(el){
956 + var c=
957 + typeof el.className==="string"
958 + ? el.className
959 + : "";
960 +
961 + return /(^|[\s_-])(box|card|tile|panel|role|check|service|source|call|area|detail|resource|topic|warn|info)([\s_-]|$)/i.test(c);
962 + }
963 +
964 + function boxLike(el){
965 + if(
966 + !visible(el,MIN_W,MIN_H)||
967 + excluded(el)
968 + ){
969 + return false;
970 + }
971 +
972 + var s=getComputedStyle(el);
973 + var r=el.getBoundingClientRect();
974 +
975 + var border=
976 + num(s.borderTopWidth)+
977 + num(s.borderRightWidth)+
978 + num(s.borderBottomWidth)+
979 + num(s.borderLeftWidth);
980 +
981 + var radius=Math.max(
982 + num(s.borderTopLeftRadius),
983 + num(s.borderTopRightRadius),
984 + num(s.borderBottomLeftRadius),
985 + num(s.borderBottomRightRadius)
986 + );
987 +
988 + var bg=
989 + s.backgroundColor&&
990 + s.backgroundColor!=="rgba(0, 0, 0, 0)"&&
991 + s.backgroundColor!=="transparent";
992 +
993 + var shadow=
994 + s.boxShadow&&
995 + s.boxShadow!=="none";
996 +
997 + return (
998 + namedLikeBox(el)||
999 + border>=1||
1000 + (bg&&radius>=3)||
1001 + (shadow&&r.width>=180)
1002 + );
1003 + }
1004 +
807 807   function pickTarget(child){
808 808   if(!visible(child,MIN_W,MIN_H))return null;
809 809  
810 - if(boxLike(child)){
811 - return child;
812 - }
1008 + if(boxLike(child))return child;
813 813  
814 814   var cr=child.getBoundingClientRect();
815 815   var q=[];
... ... @@ -871,7 +871,7 @@
871 871   return best;
872 872   }
873 873  
874 - function groupsByVisualRow(entries){
1070 + function groupRows(entries){
875 875   var groups=[];
876 876  
877 877   entries
... ... @@ -911,7 +911,7 @@
911 911   return groups;
912 912   }
913 913  
914 - function validHorizontalGroup(group){
1110 + function validRow(group){
915 915   if(group.entries.length<2)return false;
916 916  
917 917   var lefts=
... ... @@ -934,25 +934,26 @@
934 934  
935 935   if(widths.length<2)return false;
936 936  
937 - var minWidth=Math.min.apply(Math,widths);
938 - var maxWidth=Math.max.apply(Math,widths);
1133 + var minW=
1134 + Math.min.apply(Math,widths);
939 939  
1136 + var maxW=
1137 + Math.max.apply(Math,widths);
1138 +
940 940   return (
941 - maxWidth/
942 - Math.max(1,minWidth)<=
1140 + maxW/
1141 + Math.max(1,minW)<=
943 943   1.9
944 944   );
945 945   }
946 946  
947 - function normalizeText(text){
948 - return String(text||"")
949 - .replace(/s+/g," ")
950 - .trim()
951 - .toUpperCase();
952 - }
1146 + /* ---------- Known ARSN first section bars ---------- */
953 953  
954 - function exactKnownFirstSection(el){
955 - var text=normalizeText(el.textContent);
1148 + function knownBar(el){
1149 + var text=
1150 + normalizeText(
1151 + el.textContent
1152 + );
956 956  
957 957   return (
958 958   text==="HIER STARTEN"||
... ... @@ -966,150 +966,274 @@
966 966   );
967 967   }
968 968  
969 - function visuallyFirstSectionBar(card){
970 - var cardRect=card.getBoundingClientRect();
971 - var candidates=[];
1166 + function findKnownBar(card){
1167 + var cardRect=
1168 + card.getBoundingClientRect();
972 972  
973 - Array.from(
974 - card.querySelectorAll(
975 - "div,p,span,strong,h3,h4,h5,h6"
1170 + var rows=
1171 + Array.from(
1172 + card.querySelectorAll(
1173 + "div,p,span,strong,h3,h4,h5,h6"
1174 + )
976 976   )
977 - ).forEach(function(el){
978 - if(!visible(el,20,6))return;
979 - if(excluded(el))return;
1176 + .filter(function(el){
1177 + if(!visible(el,20,6))return false;
1178 + if(!knownBar(el))return false;
980 980  
981 - var r=el.getBoundingClientRect();
982 - var relativeTop=r.top-cardRect.top;
1180 + var r=el.getBoundingClientRect();
983 983  
984 - if(
985 - relativeTop<45||
986 - relativeTop>cardRect.height*.72
987 - ){
988 - return;
989 - }
1182 + var rel=
1183 + r.top-
1184 + cardRect.top;
990 990  
991 - var widthRatio=
992 - r.width/
993 - Math.max(1,cardRect.width);
1186 + return (
1187 + rel>=35&&
1188 + rel<=
1189 + cardRect.height*.75
1190 + );
1191 + })
1192 + .sort(function(a,b){
1193 + return (
1194 + a.getBoundingClientRect().top-
1195 + b.getBoundingClientRect().top
1196 + );
1197 + });
994 994  
995 - if(
996 - widthRatio<.55||
997 - widthRatio>1.05||
998 - r.height>54
999 - ){
1000 - return;
1001 - }
1199 + return rows[0]||null;
1200 + }
1002 1002  
1003 - var text=normalizeText(el.textContent);
1202 + function alignKnownBars(cards){
1203 + var rows=
1204 + cards.map(function(card){
1205 + var bar=
1206 + findKnownBar(card);
1004 1004  
1005 - if(
1006 - text.length<2||
1007 - text.length>90
1008 - ){
1009 - return;
1010 - }
1208 + if(!bar)return null;
1011 1011  
1012 - var style=getComputedStyle(el);
1210 + var cr=
1211 + card.getBoundingClientRect();
1013 1013  
1014 - var bg=
1015 - style.backgroundColor&&
1016 - style.backgroundColor!=="rgba(0, 0, 0, 0)"&&
1017 - style.backgroundColor!=="transparent";
1213 + var br=
1214 + bar.getBoundingClientRect();
1018 1018  
1019 - var border=
1020 - num(style.borderTopWidth)+
1021 - num(style.borderRightWidth)+
1022 - num(style.borderBottomWidth)+
1023 - num(style.borderLeftWidth);
1216 + return {
1217 + bar:bar,
1218 + relativeTop:
1219 + br.top-
1220 + cr.top
1221 + };
1222 + });
1024 1024  
1025 - var mostlyUpper=
1026 - text.length>=3&&
1027 - text===text.toUpperCase();
1224 + if(
1225 + rows.some(function(row){
1226 + return !row;
1227 + })
1228 + ){
1229 + return false;
1230 + }
1028 1028  
1029 - var exact=exactKnownFirstSection(el);
1232 + var target=
1233 + Math.max.apply(
1234 + Math,
1235 + rows.map(function(row){
1236 + return row.relativeTop;
1237 + })
1238 + );
1030 1030  
1031 - var score=
1032 - (exact?20:0)+
1033 - (bg?6:0)+
1034 - (border>=1?2:0)+
1035 - (mostlyUpper?4:0)+
1036 - (num(style.fontWeight)>=600?1:0);
1240 + rows.forEach(function(row){
1241 + var delta=
1242 + target-
1243 + row.relativeTop;
1037 1037  
1038 - if(score>=7){
1039 - candidates.push({
1040 - el:el,
1041 - relativeTop:relativeTop,
1042 - width:r.width,
1043 - score:score
1044 - });
1045 - }
1046 - });
1245 + if(delta<=1)return;
1047 1047  
1048 - if(!candidates.length)return null;
1247 + saveOriginal(
1248 + row.bar,
1249 + originalMarginTop,
1250 + "margin-top"
1251 + );
1049 1049  
1050 - candidates.sort(function(a,b){
1051 - if(a.score!==b.score)return b.score-a.score;
1052 - if(Math.abs(a.relativeTop-b.relativeTop)>3){
1053 - return a.relativeTop-b.relativeTop;
1054 - }
1055 - return b.width-a.width;
1056 - });
1253 + var current=
1254 + num(
1255 + getComputedStyle(
1256 + row.bar
1257 + ).marginTop
1258 + );
1057 1057  
1058 - // For exact known labels always prefer the earliest exact match.
1059 - var exacts=candidates.filter(function(c){
1060 - return exactKnownFirstSection(c.el);
1260 + row.bar.style.setProperty(
1261 + "margin-top",
1262 + (
1263 + current+
1264 + delta
1265 + )+"px"
1266 + );
1267 +
1268 + row.bar.setAttribute(
1269 + "data-sn-first-section-aligned",
1270 + "7"
1271 + );
1272 +
1273 + sectionManaged.add(
1274 + row.bar
1275 + );
1061 1061   });
1062 1062  
1063 - if(exacts.length){
1064 - exacts.sort(function(a,b){
1065 - return a.relativeTop-b.relativeTop||b.width-a.width;
1278 + return true;
1279 + }
1280 +
1281 + /* ---------- Generic first content after heading ---------- */
1282 +
1283 + function heading(card){
1284 + var cr=
1285 + card.getBoundingClientRect();
1286 +
1287 + var rows=
1288 + Array.from(
1289 + card.querySelectorAll(
1290 + "h1,h2,h3,h4,h5,h6,.box-title,[role=heading]"
1291 + )
1292 + )
1293 + .filter(function(el){
1294 + if(!visible(el,20,8))return false;
1295 +
1296 + var r=el.getBoundingClientRect();
1297 +
1298 + var rel=
1299 + r.top-
1300 + cr.top;
1301 +
1302 + return (
1303 + rel>=0&&
1304 + rel<=
1305 + Math.min(
1306 + 160,
1307 + cr.height*.45
1308 + )
1309 + );
1310 + })
1311 + .sort(function(a,b){
1312 + return (
1313 + a.getBoundingClientRect().top-
1314 + b.getBoundingClientRect().top
1315 + );
1066 1066   });
1067 - return exacts[0].el;
1068 - }
1069 1069  
1070 - // Generic fallback: earliest high-confidence bar.
1071 - candidates.sort(function(a,b){
1072 - return a.relativeTop-b.relativeTop||b.score-a.score||b.width-a.width;
1073 - });
1318 + return rows[0]||null;
1319 + }
1074 1074  
1075 - return candidates[0].el;
1321 + function firstContent(card,h){
1322 + if(!h)return null;
1323 +
1324 + var cr=
1325 + card.getBoundingClientRect();
1326 +
1327 + var hr=
1328 + h.getBoundingClientRect();
1329 +
1330 + var rows=
1331 + Array.from(
1332 + card.querySelectorAll(
1333 + "p,div,ul,ol,dl,blockquote"
1334 + )
1335 + )
1336 + .filter(function(el){
1337 + if(
1338 + el===h||
1339 + h.contains(el)||
1340 + el.contains(h)
1341 + ){
1342 + return false;
1343 + }
1344 +
1345 + if(!visible(el,30,8))return false;
1346 +
1347 + var text=
1348 + String(el.textContent||"")
1349 + .replace(/\s+/g," ")
1350 + .trim();
1351 +
1352 + if(text.length<3)return false;
1353 +
1354 + var r=el.getBoundingClientRect();
1355 +
1356 + if(
1357 + r.top<
1358 + hr.bottom-1
1359 + ){
1360 + return false;
1361 + }
1362 +
1363 + if(
1364 + r.top-cr.top>
1365 + Math.min(
1366 + 280,
1367 + cr.height*.65
1368 + )
1369 + ){
1370 + return false;
1371 + }
1372 +
1373 + if(
1374 + r.height>
1375 + cr.height*.58
1376 + ){
1377 + return false;
1378 + }
1379 +
1380 + return true;
1381 + })
1382 + .sort(function(a,b){
1383 + var ar=
1384 + a.getBoundingClientRect();
1385 +
1386 + var br=
1387 + b.getBoundingClientRect();
1388 +
1389 + return (
1390 + ar.top-br.top||
1391 + ar.height-br.height
1392 + );
1393 + });
1394 +
1395 + return rows[0]||null;
1076 1076   }
1077 1077  
1078 - function alignFirstSections(cards){
1398 + function alignGeneric(cards){
1079 1079   var rows=
1080 1080   cards.map(function(card){
1081 - var bar=
1082 - visuallyFirstSectionBar(card);
1401 + var h=
1402 + heading(card);
1083 1083  
1084 - if(!bar)return null;
1404 + var c=
1405 + firstContent(
1406 + card,
1407 + h
1408 + );
1085 1085  
1086 - var cardRect=
1410 + if(!h||!c)return null;
1411 +
1412 + var cr=
1087 1087   card.getBoundingClientRect();
1088 1088  
1089 - var barRect=
1090 - bar.getBoundingClientRect();
1415 + var rr=
1416 + c.getBoundingClientRect();
1091 1091  
1092 1092   return {
1093 - card:card,
1094 - bar:bar,
1419 + content:c,
1095 1095   relativeTop:
1096 - barRect.top-cardRect.top,
1097 - label:
1098 - normalizeText(bar.textContent)
1421 + rr.top-cr.top
1099 1099   };
1100 1100   });
1101 1101  
1102 - // Conservative: only align when every card in the horizontal row
1103 - // has a reliable first section bar.
1104 1104   if(
1105 1105   rows.some(function(row){
1106 1106   return !row;
1107 1107   })
1108 1108   ){
1109 - return null;
1430 + return false;
1110 1110   }
1111 1111  
1112 - var targetTop=
1433 + var target=
1113 1113   Math.max.apply(
1114 1114   Math,
1115 1115   rows.map(function(row){
... ... @@ -1119,54 +1119,47 @@
1119 1119  
1120 1120   rows.forEach(function(row){
1121 1121   var delta=
1122 - targetTop-
1443 + target-
1123 1123   row.relativeTop;
1124 1124  
1125 1125   if(delta<=1)return;
1126 1126  
1127 1127   saveOriginal(
1128 - row.bar,
1449 + row.content,
1129 1129   originalMarginTop,
1130 1130   "margin-top"
1131 1131   );
1132 1132  
1133 - var currentMargin=
1454 + var current=
1134 1134   num(
1135 1135   getComputedStyle(
1136 - row.bar
1457 + row.content
1137 1137   ).marginTop
1138 1138   );
1139 1139  
1140 - row.bar.style.setProperty(
1461 + row.content.style.setProperty(
1141 1141   "margin-top",
1142 1142   (
1143 - currentMargin+
1464 + current+
1144 1144   delta
1145 1145   )+"px"
1146 1146   );
1147 1147  
1148 - row.bar.setAttribute(
1149 - "data-sn-first-section-aligned",
1150 - "4"
1469 + row.content.setAttribute(
1470 + "data-sn-first-content-aligned",
1471 + "7"
1151 1151   );
1152 1152  
1153 - sectionManaged.add(row.bar);
1474 + contentManaged.add(
1475 + row.content
1476 + );
1154 1154   });
1155 1155  
1156 - return {
1157 - labels:
1158 - rows.map(function(row){
1159 - return row.label;
1160 - }),
1161 - targetRelativeTop:
1162 - Math.round(targetTop)
1163 - };
1479 + return true;
1164 1164   }
1165 1165  
1166 - function applyGroup(group){
1167 - if(!validHorizontalGroup(group)){
1168 - return null;
1169 - }
1482 + function applyRow(group){
1483 + if(!validRow(group))return false;
1170 1170  
1171 1171   var cards=[];
1172 1172   var seen=new Set();
... ... @@ -1178,25 +1178,30 @@
1178 1178   }
1179 1179   });
1180 1180  
1181 - if(cards.length<2)return null;
1495 + if(cards.length<2)return false;
1182 1182  
1183 - var firstSection=
1184 - alignFirstSections(cards);
1497 + if(
1498 + !alignKnownBars(cards)
1499 + ){
1500 + alignGeneric(cards);
1501 + }
1185 1185  
1186 - // The section alignment can change natural card heights.
1187 - // Measure only afterwards.
1188 1188   var maxHeight=0;
1189 1189  
1190 1190   cards.forEach(function(card){
1191 - maxHeight=Math.max(
1192 - maxHeight,
1193 - card.getBoundingClientRect().height
1194 - );
1506 + maxHeight=
1507 + Math.max(
1508 + maxHeight,
1509 + card.getBoundingClientRect().height
1510 + );
1195 1195   });
1196 1196  
1197 - maxHeight=Math.ceil(maxHeight);
1513 + maxHeight=
1514 + Math.ceil(
1515 + maxHeight
1516 + );
1198 1198  
1199 - if(maxHeight<MIN_H)return null;
1518 + if(maxHeight<MIN_H)return false;
1200 1200  
1201 1201   cards.forEach(function(card){
1202 1202   saveOriginal(
... ... @@ -1212,125 +1212,228 @@
1212 1212  
1213 1213   card.setAttribute(
1214 1214   "data-sn-equal-height-managed",
1215 - "4"
1534 + "7"
1216 1216   );
1217 1217  
1218 1218   heightManaged.add(card);
1219 1219   });
1220 1220  
1221 - return {
1222 - cards:cards.length,
1223 - height:maxHeight,
1224 - firstSection:firstSection
1225 - };
1540 + return true;
1226 1226   }
1227 1227  
1228 - function run(){
1229 - reset();
1543 + function calculate(force){
1544 + if(applying)return;
1230 1230  
1546 + var width=
1547 + Math.round(
1548 + window.innerWidth
1549 + );
1550 +
1551 + var meaningful=
1552 + lastWidth<0||
1553 + Math.abs(
1554 + width-
1555 + lastWidth
1556 + )>=
1557 + MIN_WIDTH_CHANGE;
1558 +
1231 1231   if(
1232 - window.innerWidth<=MOBILE_MAX
1560 + hasApplied&&
1561 + !force&&
1562 + !meaningful
1233 1233   ){
1234 1234   return;
1235 1235   }
1236 1236  
1237 - var parents=
1238 - Array.from(
1239 - root.querySelectorAll("*")
1240 - ).filter(function(parent){
1241 - return (
1242 - visible(parent,MIN_W,MIN_H)&&
1243 - parent.children&&
1244 - parent.children.length>=2&&
1245 - parent.children.length<=MAX_PARENT_CHILDREN
1246 - );
1247 - });
1567 + applying=true;
1248 1248  
1249 - var touched=new Set();
1569 + try{
1570 + harmoniseCustomCallouts();
1250 1250  
1251 - parents.forEach(function(parent){
1252 - var entries=[];
1572 + if(width<=MOBILE_MAX){
1573 + if(hasApplied){
1574 + resetRuntime();
1575 + }
1253 1253  
1254 - Array.from(parent.children).forEach(function(child){
1255 - if(!visible(child,MIN_W,MIN_H))return;
1577 + hasApplied=false;
1578 + lastWidth=width;
1579 + return;
1580 + }
1256 1256  
1257 - var target=pickTarget(child);
1258 - if(!target)return;
1582 + if(hasApplied){
1583 + resetRuntime();
1584 + }
1259 1259  
1260 - entries.push({
1261 - target:target,
1262 - rect:target.getBoundingClientRect()
1586 + var parents=
1587 + Array.from(
1588 + root.querySelectorAll("*")
1589 + )
1590 + .filter(function(parent){
1591 + return (
1592 + visible(
1593 + parent,
1594 + MIN_W,
1595 + MIN_H
1596 + )&&
1597 + parent.children&&
1598 + parent.children.length>=2&&
1599 + parent.children.length<=
1600 + MAX_PARENT_CHILDREN
1601 + );
1263 1263   });
1264 - });
1265 1265  
1266 - if(entries.length<2)return;
1604 + var touched=
1605 + new Set();
1267 1267  
1268 - groupsByVisualRow(entries).forEach(function(group){
1269 - if(!validHorizontalGroup(group))return;
1607 + parents.forEach(function(parent){
1608 + var entries=[];
1270 1270  
1271 - var signature=
1272 - group.entries.map(function(entry){
1273 - return entry.target;
1274 - });
1610 + Array.from(
1611 + parent.children
1612 + ).forEach(function(child){
1613 + if(
1614 + !visible(
1615 + child,
1616 + MIN_W,
1617 + MIN_H
1618 + )
1619 + ){
1620 + return;
1621 + }
1275 1275  
1276 - if(
1277 - !signature.some(function(el){
1278 - return !touched.has(el);
1279 - })
1280 - ){
1281 - return;
1282 - }
1623 + var target=
1624 + pickTarget(child);
1283 1283  
1284 - var applied=
1285 - applyGroup(group);
1626 + if(!target)return;
1286 1286  
1287 - if(applied){
1288 - signature.forEach(function(el){
1289 - touched.add(el);
1628 + entries.push({
1629 + target:target,
1630 + rect:
1631 + target.getBoundingClientRect()
1290 1290   });
1291 - }
1633 + });
1634 +
1635 + groupRows(
1636 + entries
1637 + ).forEach(function(group){
1638 + if(!validRow(group))return;
1639 +
1640 + var targets=
1641 + group.entries.map(function(entry){
1642 + return entry.target;
1643 + });
1644 +
1645 + if(
1646 + !targets.some(function(el){
1647 + return !touched.has(el);
1648 + })
1649 + ){
1650 + return;
1651 + }
1652 +
1653 + if(applyRow(group)){
1654 + targets.forEach(function(el){
1655 + touched.add(el);
1656 + });
1657 + }
1658 + });
1292 1292   });
1293 - });
1660 +
1661 + lastWidth=width;
1662 + hasApplied=true;
1663 + }finally{
1664 + applying=false;
1665 + }
1294 1294   }
1295 1295  
1296 - function schedule(){
1297 - if(raf)cancelAnimationFrame(raf);
1668 + function resize(){
1669 + if(resizeTimer){
1670 + clearTimeout(
1671 + resizeTimer
1672 + );
1673 + }
1298 1298  
1299 - raf=requestAnimationFrame(function(){
1300 - raf=requestAnimationFrame(function(){
1301 - raf=0;
1302 - run();
1303 - });
1304 - });
1675 + resizeTimer=
1676 + setTimeout(
1677 + function(){
1678 + resizeTimer=0;
1679 + calculate(false);
1680 + },
1681 + RESIZE_DEBOUNCE
1682 + );
1305 1305   }
1306 1306  
1307 - addEventListener("resize",schedule,{passive:true});
1308 - addEventListener("load",schedule,{once:true});
1685 + function waitImages(){
1686 + var images=
1687 + Array.from(
1688 + root.querySelectorAll("img")
1689 + );
1309 1309  
1310 - root.addEventListener(
1311 - "click",
1312 - function(){
1313 - setTimeout(schedule,0);
1314 - },
1315 - true
1316 - );
1691 + return Promise.all(
1692 + images.map(function(img){
1693 + if(img.complete){
1694 + return Promise.resolve();
1695 + }
1317 1317  
1318 - Array.from(root.querySelectorAll("img")).forEach(function(img){
1319 - if(!img.complete){
1320 - img.addEventListener("load",schedule,{once:true});
1697 + return new Promise(function(resolve){
1698 + var done=function(){
1699 + resolve();
1700 + };
1701 +
1702 + img.addEventListener(
1703 + "load",
1704 + done,
1705 + {once:true}
1706 + );
1707 +
1708 + img.addEventListener(
1709 + "error",
1710 + done,
1711 + {once:true}
1712 + );
1713 + });
1714 + })
1715 + );
1716 + }
1717 +
1718 + async function initial(){
1719 + if(
1720 + document.readyState!==
1721 + "complete"
1722 + ){
1723 + await new Promise(function(resolve){
1724 + addEventListener(
1725 + "load",
1726 + resolve,
1727 + {once:true}
1728 + );
1729 + });
1321 1321   }
1322 - });
1323 1323  
1324 - if(document.fonts&&document.fonts.ready){
1325 - document.fonts.ready.then(schedule);
1732 + if(
1733 + document.fonts&&
1734 + document.fonts.ready
1735 + ){
1736 + try{
1737 + await document.fonts.ready;
1738 + }catch(error){}
1739 + }
1740 +
1741 + await waitImages();
1742 +
1743 + // Exactly one initial layout calculation.
1744 + calculate(true);
1326 1326   }
1327 1327  
1328 - schedule();
1329 - setTimeout(schedule,200);
1330 - setTimeout(schedule,650);
1331 - setTimeout(schedule,1400);
1747 + addEventListener(
1748 + "resize",
1749 + resize,
1750 + {passive:true}
1751 + );
1752 +
1753 + initial();
1332 1332  })();
1333 1333  </script>
1334 -<!-- SN_HORIZONTAL_BOX_HARMONIZER_END -->
1335 -{{/html}}
1336 1336  
1757 +<!-- SN_HORIZONTAL_BOX_HARMONIZER_END -->
1758 +{{/html}}\n