Changes for page Plan Your Data Management

Last modified by Veronika Kocher on Friday, 04 Sep 2026 14:45

From version 4.9
edited by Veronika Kocher
on Thursday, 03 Sep 2026 9:34
Change comment: There is no comment for this version
To version 4.10
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
... ... @@ -257,15 +257,104 @@
257 257  </div>
258 258  {{/html}}
259 259  
260 -{{html clean="false"}}
260 +\n\n
261 +\n\n{{html clean="false"}}
261 261  <!-- SN_HORIZONTAL_BOX_HARMONIZER_START -->
262 -<style data-sn-horizontal-box-harmonizer="4">
263 -[data-sn-equal-height-managed],
264 -[data-sn-first-section-aligned]{
263 +
264 +<style data-sn-horizontal-box-harmonizer="7">
265 +/* ---------- Typography: never justify headings ---------- */
266 +#xwikicontent h1,
267 +#xwikicontent h2,
268 +#xwikicontent h3,
269 +#xwikicontent h4,
270 +#xwikicontent h5,
271 +#xwikicontent h6,
272 +#xwikicontent .box-title,
273 +#xwikicontent [role="heading"],
274 +#xwikicontent h1 a,
275 +#xwikicontent h2 a,
276 +#xwikicontent h3 a,
277 +#xwikicontent h4 a,
278 +#xwikicontent h5 a,
279 +#xwikicontent h6 a,
280 +#xwikicontent .box-title a,
281 +#xwikicontent [role="heading"] a{
282 + text-align:left!important;
283 + text-justify:auto!important;
284 + word-spacing:normal!important;
285 +}
286 +
287 +/* ---------- Native XWiki semantic callouts ---------- */
288 +#xwikicontent .box.infomessage,
289 +#xwikicontent .infomessage,
290 +#xwikicontent .box.warningmessage,
291 +#xwikicontent .warningmessage,
292 +#xwikicontent .box.successmessage,
293 +#xwikicontent .successmessage,
294 +#xwikicontent .box.errormessage,
295 +#xwikicontent .errormessage,
296 +#xwikicontent [data-sn-callout-harmonized="1"]{
297 + box-sizing:border-box!important;
298 + border-radius:8px!important;
299 + border-top:1px solid rgba(0,0,0,.08)!important;
300 + border-right:1px solid rgba(0,0,0,.08)!important;
301 + border-bottom:1px solid rgba(0,0,0,.08)!important;
302 + border-left:4px solid var(--sn-callout-accent,#21b6d7)!important;
303 + padding:14px 16px!important;
304 + text-align:left!important;
305 + text-justify:auto!important;
306 + word-spacing:normal!important;
307 + box-shadow:none!important;
308 +}
309 +
310 +#xwikicontent .box.infomessage,
311 +#xwikicontent .infomessage{
312 + --sn-callout-accent:#21b6d7;
313 + background:#eaf7fb!important;
314 +}
315 +
316 +#xwikicontent .box.warningmessage,
317 +#xwikicontent .warningmessage{
318 + --sn-callout-accent:#f0aa00;
319 + background:#fff8e7!important;
320 +}
321 +
322 +#xwikicontent .box.successmessage,
323 +#xwikicontent .successmessage{
324 + --sn-callout-accent:#38b76a;
325 + background:#edf9f1!important;
326 +}
327 +
328 +#xwikicontent .box.errormessage,
329 +#xwikicontent .errormessage{
330 + --sn-callout-accent:#e94b13;
331 + background:#fff1ec!important;
332 +}
333 +
334 +#xwikicontent [data-sn-callout-harmonized="1"] > :first-child,
335 +#xwikicontent .infomessage > :first-child,
336 +#xwikicontent .warningmessage > :first-child,
337 +#xwikicontent .successmessage > :first-child,
338 +#xwikicontent .errormessage > :first-child{
339 + margin-top:0!important;
340 +}
341 +
342 +#xwikicontent [data-sn-callout-harmonized="1"] > :last-child,
343 +#xwikicontent .infomessage > :last-child,
344 +#xwikicontent .warningmessage > :last-child,
345 +#xwikicontent .successmessage > :last-child,
346 +#xwikicontent .errormessage > :last-child{
347 + margin-bottom:0!important;
348 +}
349 +
350 +#xwikicontent [data-sn-equal-height-managed],
351 +#xwikicontent [data-sn-first-section-aligned],
352 +#xwikicontent [data-sn-first-content-aligned]{
265 265   box-sizing:border-box;
266 266  }
267 267  </style>
268 -<script data-sn-horizontal-box-harmonizer="4">
356 +
357 +<script data-sn-horizontal-box-harmonizer="7">
269 269  (function(){
270 270   "use strict";
271 271  
... ... @@ -273,8 +273,10 @@
273 273   var ROW_TOLERANCE=6;
274 274   var MIN_W=150;
275 275   var MIN_H=36;
276 - var MAX_PARENT_CHILDREN=16;
365 + var MAX_PARENT_CHILDREN=18;
277 277   var SEARCH_DEPTH=5;
367 + var RESIZE_DEBOUNCE=220;
368 + var MIN_WIDTH_CHANGE=2;
278 278  
279 279   var root=
280 280   document.getElementById("xwikicontent")||
... ... @@ -286,10 +286,16 @@
286 286  
287 287   var heightManaged=new Set();
288 288   var sectionManaged=new Set();
380 + var contentManaged=new Set();
381 +
289 289   var originalMinHeight=new WeakMap();
290 290   var originalMarginTop=new WeakMap();
291 - var raf=0;
292 292  
385 + var resizeTimer=0;
386 + var lastWidth=-1;
387 + var hasApplied=false;
388 + var applying=false;
389 +
293 293   function num(v){
294 294   var n=parseFloat(v);
295 295   return Number.isFinite(n)?n:0;
... ... @@ -316,6 +316,13 @@
316 316   );
317 317   }
318 318  
416 + function normalizeText(text){
417 + return String(text||"")
418 + .replace(/\s+/g," ")
419 + .trim()
420 + .toUpperCase();
421 + }
422 +
319 319   function excluded(el){
320 320   return (
321 321   !el.matches||
... ... @@ -325,57 +325,84 @@
325 325   );
326 326   }
327 327  
328 - function namedLikeBox(el){
432 + /* ---------- Custom coloured callouts ---------- */
433 +
434 + function nativeCallout(el){
329 329   var c=
330 330   typeof el.className==="string"
331 - ? el.className
437 + ? el.className.toLowerCase()
332 332   : "";
333 333  
334 - return /(^|[s_-])(box|card|tile|panel|role|check|service|source|call|area|detail|resource|topic|warn|info)([s_-]|$)/i.test(c);
440 + return (
441 + /(^|\s)(infomessage|warningmessage|successmessage|errormessage)(\s|$)/.test(c)
442 + );
335 335   }
336 336  
337 - function boxLike(el){
338 - if(!visible(el,MIN_W,MIN_H)||excluded(el))return false;
445 + function customCallout(el){
446 + if(!visible(el,220,42)||excluded(el))return false;
339 339  
340 - var s=getComputedStyle(el);
341 341   var r=el.getBoundingClientRect();
342 342  
343 - var border=
344 - num(s.borderTopWidth)+
345 - num(s.borderRightWidth)+
346 - num(s.borderBottomWidth)+
347 - num(s.borderLeftWidth);
450 + if(r.height>520)return false;
348 348  
349 - var radius=Math.max(
350 - num(s.borderTopLeftRadius),
351 - num(s.borderTopRightRadius),
352 - num(s.borderBottomLeftRadius),
353 - num(s.borderBottomRightRadius)
354 - );
452 + var s=getComputedStyle(el);
355 355  
356 356   var bg=
357 357   s.backgroundColor&&
358 358   s.backgroundColor!=="rgba(0, 0, 0, 0)"&&
359 - s.backgroundColor!=="transparent";
457 + s.backgroundColor!=="transparent"&&
458 + s.backgroundColor!=="rgb(255, 255, 255)";
360 360  
361 - var shadow=
362 - s.boxShadow&&
363 - s.boxShadow!=="none";
460 + var left=
461 + num(s.borderLeftWidth);
364 364  
365 365   return (
366 - namedLikeBox(el)||
367 - border>=1||
368 - (bg&&radius>=3)||
369 - (shadow&&r.width>=180)
464 + bg&&
465 + left>=2
370 370   );
371 371   }
372 372  
469 + function harmoniseCustomCallouts(){
470 + Array.from(
471 + root.querySelectorAll(
472 + "div,aside,section"
473 + )
474 + ).forEach(function(el){
475 + if(nativeCallout(el))return;
476 + if(!customCallout(el))return;
477 +
478 + var s=getComputedStyle(el);
479 +
480 + el.setAttribute(
481 + "data-sn-callout-harmonized",
482 + "1"
483 + );
484 +
485 + if(
486 + s.borderLeftColor
487 + ){
488 + el.style.setProperty(
489 + "--sn-callout-accent",
490 + s.borderLeftColor
491 + );
492 + }
493 + });
494 + }
495 +
496 + /* ---------- Restore only values V7 itself changed ---------- */
497 +
373 373   function saveOriginal(el,map,property){
374 374   if(map.has(el))return;
375 375  
376 376   map.set(el,{
377 - value:el.style.getPropertyValue(property),
378 - priority:el.style.getPropertyPriority(property)
502 + value:
503 + el.style.getPropertyValue(
504 + property
505 + ),
506 + priority:
507 + el.style.getPropertyPriority(
508 + property
509 + )
379 379   });
380 380   }
381 381  
... ... @@ -389,11 +389,13 @@
389 389   original.priority||""
390 390   );
391 391   }else{
392 - el.style.removeProperty(property);
523 + el.style.removeProperty(
524 + property
525 + );
393 393   }
394 394   }
395 395  
396 - function reset(){
529 + function resetRuntime(){
397 397   heightManaged.forEach(function(el){
398 398   restoreOriginal(
399 399   el,
... ... @@ -418,16 +418,79 @@
418 418   );
419 419   });
420 420  
554 + contentManaged.forEach(function(el){
555 + restoreOriginal(
556 + el,
557 + originalMarginTop,
558 + "margin-top"
559 + );
560 +
561 + el.removeAttribute(
562 + "data-sn-first-content-aligned"
563 + );
564 + });
565 +
421 421   heightManaged.clear();
422 422   sectionManaged.clear();
568 + contentManaged.clear();
423 423   }
424 424  
571 + /* ---------- Card detection ---------- */
572 +
573 + function namedLikeBox(el){
574 + var c=
575 + typeof el.className==="string"
576 + ? el.className
577 + : "";
578 +
579 + return /(^|[\s_-])(box|card|tile|panel|role|check|service|source|call|area|detail|resource|topic|warn|info)([\s_-]|$)/i.test(c);
580 + }
581 +
582 + function boxLike(el){
583 + if(
584 + !visible(el,MIN_W,MIN_H)||
585 + excluded(el)
586 + ){
587 + return false;
588 + }
589 +
590 + var s=getComputedStyle(el);
591 + var r=el.getBoundingClientRect();
592 +
593 + var border=
594 + num(s.borderTopWidth)+
595 + num(s.borderRightWidth)+
596 + num(s.borderBottomWidth)+
597 + num(s.borderLeftWidth);
598 +
599 + var radius=Math.max(
600 + num(s.borderTopLeftRadius),
601 + num(s.borderTopRightRadius),
602 + num(s.borderBottomLeftRadius),
603 + num(s.borderBottomRightRadius)
604 + );
605 +
606 + var bg=
607 + s.backgroundColor&&
608 + s.backgroundColor!=="rgba(0, 0, 0, 0)"&&
609 + s.backgroundColor!=="transparent";
610 +
611 + var shadow=
612 + s.boxShadow&&
613 + s.boxShadow!=="none";
614 +
615 + return (
616 + namedLikeBox(el)||
617 + border>=1||
618 + (bg&&radius>=3)||
619 + (shadow&&r.width>=180)
620 + );
621 + }
622 +
425 425   function pickTarget(child){
426 426   if(!visible(child,MIN_W,MIN_H))return null;
427 427  
428 - if(boxLike(child)){
429 - return child;
430 - }
626 + if(boxLike(child))return child;
431 431  
432 432   var cr=child.getBoundingClientRect();
433 433   var q=[];
... ... @@ -489,7 +489,7 @@
489 489   return best;
490 490   }
491 491  
492 - function groupsByVisualRow(entries){
688 + function groupRows(entries){
493 493   var groups=[];
494 494  
495 495   entries
... ... @@ -529,7 +529,7 @@
529 529   return groups;
530 530   }
531 531  
532 - function validHorizontalGroup(group){
728 + function validRow(group){
533 533   if(group.entries.length<2)return false;
534 534  
535 535   var lefts=
... ... @@ -552,25 +552,26 @@
552 552  
553 553   if(widths.length<2)return false;
554 554  
555 - var minWidth=Math.min.apply(Math,widths);
556 - var maxWidth=Math.max.apply(Math,widths);
751 + var minW=
752 + Math.min.apply(Math,widths);
557 557  
754 + var maxW=
755 + Math.max.apply(Math,widths);
756 +
558 558   return (
559 - maxWidth/
560 - Math.max(1,minWidth)<=
758 + maxW/
759 + Math.max(1,minW)<=
561 561   1.9
562 562   );
563 563   }
564 564  
565 - function normalizeText(text){
566 - return String(text||"")
567 - .replace(/s+/g," ")
568 - .trim()
569 - .toUpperCase();
570 - }
764 + /* ---------- Known ARSN first section bars ---------- */
571 571  
572 - function exactKnownFirstSection(el){
573 - var text=normalizeText(el.textContent);
766 + function knownBar(el){
767 + var text=
768 + normalizeText(
769 + el.textContent
770 + );
574 574  
575 575   return (
576 576   text==="HIER STARTEN"||
... ... @@ -584,150 +584,274 @@
584 584   );
585 585   }
586 586  
587 - function visuallyFirstSectionBar(card){
588 - var cardRect=card.getBoundingClientRect();
589 - var candidates=[];
784 + function findKnownBar(card){
785 + var cardRect=
786 + card.getBoundingClientRect();
590 590  
591 - Array.from(
592 - card.querySelectorAll(
593 - "div,p,span,strong,h3,h4,h5,h6"
788 + var rows=
789 + Array.from(
790 + card.querySelectorAll(
791 + "div,p,span,strong,h3,h4,h5,h6"
792 + )
594 594   )
595 - ).forEach(function(el){
596 - if(!visible(el,20,6))return;
597 - if(excluded(el))return;
794 + .filter(function(el){
795 + if(!visible(el,20,6))return false;
796 + if(!knownBar(el))return false;
598 598  
599 - var r=el.getBoundingClientRect();
600 - var relativeTop=r.top-cardRect.top;
798 + var r=el.getBoundingClientRect();
601 601  
602 - if(
603 - relativeTop<45||
604 - relativeTop>cardRect.height*.72
605 - ){
606 - return;
607 - }
800 + var rel=
801 + r.top-
802 + cardRect.top;
608 608  
609 - var widthRatio=
610 - r.width/
611 - Math.max(1,cardRect.width);
804 + return (
805 + rel>=35&&
806 + rel<=
807 + cardRect.height*.75
808 + );
809 + })
810 + .sort(function(a,b){
811 + return (
812 + a.getBoundingClientRect().top-
813 + b.getBoundingClientRect().top
814 + );
815 + });
612 612  
613 - if(
614 - widthRatio<.55||
615 - widthRatio>1.05||
616 - r.height>54
617 - ){
618 - return;
619 - }
817 + return rows[0]||null;
818 + }
620 620  
621 - var text=normalizeText(el.textContent);
820 + function alignKnownBars(cards){
821 + var rows=
822 + cards.map(function(card){
823 + var bar=
824 + findKnownBar(card);
622 622  
623 - if(
624 - text.length<2||
625 - text.length>90
626 - ){
627 - return;
628 - }
826 + if(!bar)return null;
629 629  
630 - var style=getComputedStyle(el);
828 + var cr=
829 + card.getBoundingClientRect();
631 631  
632 - var bg=
633 - style.backgroundColor&&
634 - style.backgroundColor!=="rgba(0, 0, 0, 0)"&&
635 - style.backgroundColor!=="transparent";
831 + var br=
832 + bar.getBoundingClientRect();
636 636  
637 - var border=
638 - num(style.borderTopWidth)+
639 - num(style.borderRightWidth)+
640 - num(style.borderBottomWidth)+
641 - num(style.borderLeftWidth);
834 + return {
835 + bar:bar,
836 + relativeTop:
837 + br.top-
838 + cr.top
839 + };
840 + });
642 642  
643 - var mostlyUpper=
644 - text.length>=3&&
645 - text===text.toUpperCase();
842 + if(
843 + rows.some(function(row){
844 + return !row;
845 + })
846 + ){
847 + return false;
848 + }
646 646  
647 - var exact=exactKnownFirstSection(el);
850 + var target=
851 + Math.max.apply(
852 + Math,
853 + rows.map(function(row){
854 + return row.relativeTop;
855 + })
856 + );
648 648  
649 - var score=
650 - (exact?20:0)+
651 - (bg?6:0)+
652 - (border>=1?2:0)+
653 - (mostlyUpper?4:0)+
654 - (num(style.fontWeight)>=600?1:0);
858 + rows.forEach(function(row){
859 + var delta=
860 + target-
861 + row.relativeTop;
655 655  
656 - if(score>=7){
657 - candidates.push({
658 - el:el,
659 - relativeTop:relativeTop,
660 - width:r.width,
661 - score:score
662 - });
663 - }
664 - });
863 + if(delta<=1)return;
665 665  
666 - if(!candidates.length)return null;
865 + saveOriginal(
866 + row.bar,
867 + originalMarginTop,
868 + "margin-top"
869 + );
667 667  
668 - candidates.sort(function(a,b){
669 - if(a.score!==b.score)return b.score-a.score;
670 - if(Math.abs(a.relativeTop-b.relativeTop)>3){
671 - return a.relativeTop-b.relativeTop;
672 - }
673 - return b.width-a.width;
674 - });
871 + var current=
872 + num(
873 + getComputedStyle(
874 + row.bar
875 + ).marginTop
876 + );
675 675  
676 - // For exact known labels always prefer the earliest exact match.
677 - var exacts=candidates.filter(function(c){
678 - return exactKnownFirstSection(c.el);
878 + row.bar.style.setProperty(
879 + "margin-top",
880 + (
881 + current+
882 + delta
883 + )+"px"
884 + );
885 +
886 + row.bar.setAttribute(
887 + "data-sn-first-section-aligned",
888 + "7"
889 + );
890 +
891 + sectionManaged.add(
892 + row.bar
893 + );
679 679   });
680 680  
681 - if(exacts.length){
682 - exacts.sort(function(a,b){
683 - return a.relativeTop-b.relativeTop||b.width-a.width;
896 + return true;
897 + }
898 +
899 + /* ---------- Generic first content after heading ---------- */
900 +
901 + function heading(card){
902 + var cr=
903 + card.getBoundingClientRect();
904 +
905 + var rows=
906 + Array.from(
907 + card.querySelectorAll(
908 + "h1,h2,h3,h4,h5,h6,.box-title,[role=heading]"
909 + )
910 + )
911 + .filter(function(el){
912 + if(!visible(el,20,8))return false;
913 +
914 + var r=el.getBoundingClientRect();
915 +
916 + var rel=
917 + r.top-
918 + cr.top;
919 +
920 + return (
921 + rel>=0&&
922 + rel<=
923 + Math.min(
924 + 160,
925 + cr.height*.45
926 + )
927 + );
928 + })
929 + .sort(function(a,b){
930 + return (
931 + a.getBoundingClientRect().top-
932 + b.getBoundingClientRect().top
933 + );
684 684   });
685 - return exacts[0].el;
686 - }
687 687  
688 - // Generic fallback: earliest high-confidence bar.
689 - candidates.sort(function(a,b){
690 - return a.relativeTop-b.relativeTop||b.score-a.score||b.width-a.width;
691 - });
936 + return rows[0]||null;
937 + }
692 692  
693 - return candidates[0].el;
939 + function firstContent(card,h){
940 + if(!h)return null;
941 +
942 + var cr=
943 + card.getBoundingClientRect();
944 +
945 + var hr=
946 + h.getBoundingClientRect();
947 +
948 + var rows=
949 + Array.from(
950 + card.querySelectorAll(
951 + "p,div,ul,ol,dl,blockquote"
952 + )
953 + )
954 + .filter(function(el){
955 + if(
956 + el===h||
957 + h.contains(el)||
958 + el.contains(h)
959 + ){
960 + return false;
961 + }
962 +
963 + if(!visible(el,30,8))return false;
964 +
965 + var text=
966 + String(el.textContent||"")
967 + .replace(/\s+/g," ")
968 + .trim();
969 +
970 + if(text.length<3)return false;
971 +
972 + var r=el.getBoundingClientRect();
973 +
974 + if(
975 + r.top<
976 + hr.bottom-1
977 + ){
978 + return false;
979 + }
980 +
981 + if(
982 + r.top-cr.top>
983 + Math.min(
984 + 280,
985 + cr.height*.65
986 + )
987 + ){
988 + return false;
989 + }
990 +
991 + if(
992 + r.height>
993 + cr.height*.58
994 + ){
995 + return false;
996 + }
997 +
998 + return true;
999 + })
1000 + .sort(function(a,b){
1001 + var ar=
1002 + a.getBoundingClientRect();
1003 +
1004 + var br=
1005 + b.getBoundingClientRect();
1006 +
1007 + return (
1008 + ar.top-br.top||
1009 + ar.height-br.height
1010 + );
1011 + });
1012 +
1013 + return rows[0]||null;
694 694   }
695 695  
696 - function alignFirstSections(cards){
1016 + function alignGeneric(cards){
697 697   var rows=
698 698   cards.map(function(card){
699 - var bar=
700 - visuallyFirstSectionBar(card);
1019 + var h=
1020 + heading(card);
701 701  
702 - if(!bar)return null;
1022 + var c=
1023 + firstContent(
1024 + card,
1025 + h
1026 + );
703 703  
704 - var cardRect=
1028 + if(!h||!c)return null;
1029 +
1030 + var cr=
705 705   card.getBoundingClientRect();
706 706  
707 - var barRect=
708 - bar.getBoundingClientRect();
1033 + var rr=
1034 + c.getBoundingClientRect();
709 709  
710 710   return {
711 - card:card,
712 - bar:bar,
1037 + content:c,
713 713   relativeTop:
714 - barRect.top-cardRect.top,
715 - label:
716 - normalizeText(bar.textContent)
1039 + rr.top-cr.top
717 717   };
718 718   });
719 719  
720 - // Conservative: only align when every card in the horizontal row
721 - // has a reliable first section bar.
722 722   if(
723 723   rows.some(function(row){
724 724   return !row;
725 725   })
726 726   ){
727 - return null;
1048 + return false;
728 728   }
729 729  
730 - var targetTop=
1051 + var target=
731 731   Math.max.apply(
732 732   Math,
733 733   rows.map(function(row){
... ... @@ -737,54 +737,47 @@
737 737  
738 738   rows.forEach(function(row){
739 739   var delta=
740 - targetTop-
1061 + target-
741 741   row.relativeTop;
742 742  
743 743   if(delta<=1)return;
744 744  
745 745   saveOriginal(
746 - row.bar,
1067 + row.content,
747 747   originalMarginTop,
748 748   "margin-top"
749 749   );
750 750  
751 - var currentMargin=
1072 + var current=
752 752   num(
753 753   getComputedStyle(
754 - row.bar
1075 + row.content
755 755   ).marginTop
756 756   );
757 757  
758 - row.bar.style.setProperty(
1079 + row.content.style.setProperty(
759 759   "margin-top",
760 760   (
761 - currentMargin+
1082 + current+
762 762   delta
763 763   )+"px"
764 764   );
765 765  
766 - row.bar.setAttribute(
767 - "data-sn-first-section-aligned",
768 - "4"
1087 + row.content.setAttribute(
1088 + "data-sn-first-content-aligned",
1089 + "7"
769 769   );
770 770  
771 - sectionManaged.add(row.bar);
1092 + contentManaged.add(
1093 + row.content
1094 + );
772 772   });
773 773  
774 - return {
775 - labels:
776 - rows.map(function(row){
777 - return row.label;
778 - }),
779 - targetRelativeTop:
780 - Math.round(targetTop)
781 - };
1097 + return true;
782 782   }
783 783  
784 - function applyGroup(group){
785 - if(!validHorizontalGroup(group)){
786 - return null;
787 - }
1100 + function applyRow(group){
1101 + if(!validRow(group))return false;
788 788  
789 789   var cards=[];
790 790   var seen=new Set();
... ... @@ -796,25 +796,30 @@
796 796   }
797 797   });
798 798  
799 - if(cards.length<2)return null;
1113 + if(cards.length<2)return false;
800 800  
801 - var firstSection=
802 - alignFirstSections(cards);
1115 + if(
1116 + !alignKnownBars(cards)
1117 + ){
1118 + alignGeneric(cards);
1119 + }
803 803  
804 - // The section alignment can change natural card heights.
805 - // Measure only afterwards.
806 806   var maxHeight=0;
807 807  
808 808   cards.forEach(function(card){
809 - maxHeight=Math.max(
810 - maxHeight,
811 - card.getBoundingClientRect().height
812 - );
1124 + maxHeight=
1125 + Math.max(
1126 + maxHeight,
1127 + card.getBoundingClientRect().height
1128 + );
813 813   });
814 814  
815 - maxHeight=Math.ceil(maxHeight);
1131 + maxHeight=
1132 + Math.ceil(
1133 + maxHeight
1134 + );
816 816  
817 - if(maxHeight<MIN_H)return null;
1136 + if(maxHeight<MIN_H)return false;
818 818  
819 819   cards.forEach(function(card){
820 820   saveOriginal(
... ... @@ -830,125 +830,228 @@
830 830  
831 831   card.setAttribute(
832 832   "data-sn-equal-height-managed",
833 - "4"
1152 + "7"
834 834   );
835 835  
836 836   heightManaged.add(card);
837 837   });
838 838  
839 - return {
840 - cards:cards.length,
841 - height:maxHeight,
842 - firstSection:firstSection
843 - };
1158 + return true;
844 844   }
845 845  
846 - function run(){
847 - reset();
1161 + function calculate(force){
1162 + if(applying)return;
848 848  
1164 + var width=
1165 + Math.round(
1166 + window.innerWidth
1167 + );
1168 +
1169 + var meaningful=
1170 + lastWidth<0||
1171 + Math.abs(
1172 + width-
1173 + lastWidth
1174 + )>=
1175 + MIN_WIDTH_CHANGE;
1176 +
849 849   if(
850 - window.innerWidth<=MOBILE_MAX
1178 + hasApplied&&
1179 + !force&&
1180 + !meaningful
851 851   ){
852 852   return;
853 853   }
854 854  
855 - var parents=
856 - Array.from(
857 - root.querySelectorAll("*")
858 - ).filter(function(parent){
859 - return (
860 - visible(parent,MIN_W,MIN_H)&&
861 - parent.children&&
862 - parent.children.length>=2&&
863 - parent.children.length<=MAX_PARENT_CHILDREN
864 - );
865 - });
1185 + applying=true;
866 866  
867 - var touched=new Set();
1187 + try{
1188 + harmoniseCustomCallouts();
868 868  
869 - parents.forEach(function(parent){
870 - var entries=[];
1190 + if(width<=MOBILE_MAX){
1191 + if(hasApplied){
1192 + resetRuntime();
1193 + }
871 871  
872 - Array.from(parent.children).forEach(function(child){
873 - if(!visible(child,MIN_W,MIN_H))return;
1195 + hasApplied=false;
1196 + lastWidth=width;
1197 + return;
1198 + }
874 874  
875 - var target=pickTarget(child);
876 - if(!target)return;
1200 + if(hasApplied){
1201 + resetRuntime();
1202 + }
877 877  
878 - entries.push({
879 - target:target,
880 - rect:target.getBoundingClientRect()
1204 + var parents=
1205 + Array.from(
1206 + root.querySelectorAll("*")
1207 + )
1208 + .filter(function(parent){
1209 + return (
1210 + visible(
1211 + parent,
1212 + MIN_W,
1213 + MIN_H
1214 + )&&
1215 + parent.children&&
1216 + parent.children.length>=2&&
1217 + parent.children.length<=
1218 + MAX_PARENT_CHILDREN
1219 + );
881 881   });
882 - });
883 883  
884 - if(entries.length<2)return;
1222 + var touched=
1223 + new Set();
885 885  
886 - groupsByVisualRow(entries).forEach(function(group){
887 - if(!validHorizontalGroup(group))return;
1225 + parents.forEach(function(parent){
1226 + var entries=[];
888 888  
889 - var signature=
890 - group.entries.map(function(entry){
891 - return entry.target;
892 - });
1228 + Array.from(
1229 + parent.children
1230 + ).forEach(function(child){
1231 + if(
1232 + !visible(
1233 + child,
1234 + MIN_W,
1235 + MIN_H
1236 + )
1237 + ){
1238 + return;
1239 + }
893 893  
894 - if(
895 - !signature.some(function(el){
896 - return !touched.has(el);
897 - })
898 - ){
899 - return;
900 - }
1241 + var target=
1242 + pickTarget(child);
901 901  
902 - var applied=
903 - applyGroup(group);
1244 + if(!target)return;
904 904  
905 - if(applied){
906 - signature.forEach(function(el){
907 - touched.add(el);
1246 + entries.push({
1247 + target:target,
1248 + rect:
1249 + target.getBoundingClientRect()
908 908   });
909 - }
1251 + });
1252 +
1253 + groupRows(
1254 + entries
1255 + ).forEach(function(group){
1256 + if(!validRow(group))return;
1257 +
1258 + var targets=
1259 + group.entries.map(function(entry){
1260 + return entry.target;
1261 + });
1262 +
1263 + if(
1264 + !targets.some(function(el){
1265 + return !touched.has(el);
1266 + })
1267 + ){
1268 + return;
1269 + }
1270 +
1271 + if(applyRow(group)){
1272 + targets.forEach(function(el){
1273 + touched.add(el);
1274 + });
1275 + }
1276 + });
910 910   });
911 - });
1278 +
1279 + lastWidth=width;
1280 + hasApplied=true;
1281 + }finally{
1282 + applying=false;
1283 + }
912 912   }
913 913  
914 - function schedule(){
915 - if(raf)cancelAnimationFrame(raf);
1286 + function resize(){
1287 + if(resizeTimer){
1288 + clearTimeout(
1289 + resizeTimer
1290 + );
1291 + }
916 916  
917 - raf=requestAnimationFrame(function(){
918 - raf=requestAnimationFrame(function(){
919 - raf=0;
920 - run();
921 - });
922 - });
1293 + resizeTimer=
1294 + setTimeout(
1295 + function(){
1296 + resizeTimer=0;
1297 + calculate(false);
1298 + },
1299 + RESIZE_DEBOUNCE
1300 + );
923 923   }
924 924  
925 - addEventListener("resize",schedule,{passive:true});
926 - addEventListener("load",schedule,{once:true});
1303 + function waitImages(){
1304 + var images=
1305 + Array.from(
1306 + root.querySelectorAll("img")
1307 + );
927 927  
928 - root.addEventListener(
929 - "click",
930 - function(){
931 - setTimeout(schedule,0);
932 - },
933 - true
934 - );
1309 + return Promise.all(
1310 + images.map(function(img){
1311 + if(img.complete){
1312 + return Promise.resolve();
1313 + }
935 935  
936 - Array.from(root.querySelectorAll("img")).forEach(function(img){
937 - if(!img.complete){
938 - img.addEventListener("load",schedule,{once:true});
1315 + return new Promise(function(resolve){
1316 + var done=function(){
1317 + resolve();
1318 + };
1319 +
1320 + img.addEventListener(
1321 + "load",
1322 + done,
1323 + {once:true}
1324 + );
1325 +
1326 + img.addEventListener(
1327 + "error",
1328 + done,
1329 + {once:true}
1330 + );
1331 + });
1332 + })
1333 + );
1334 + }
1335 +
1336 + async function initial(){
1337 + if(
1338 + document.readyState!==
1339 + "complete"
1340 + ){
1341 + await new Promise(function(resolve){
1342 + addEventListener(
1343 + "load",
1344 + resolve,
1345 + {once:true}
1346 + );
1347 + });
939 939   }
940 - });
941 941  
942 - if(document.fonts&&document.fonts.ready){
943 - document.fonts.ready.then(schedule);
1350 + if(
1351 + document.fonts&&
1352 + document.fonts.ready
1353 + ){
1354 + try{
1355 + await document.fonts.ready;
1356 + }catch(error){}
1357 + }
1358 +
1359 + await waitImages();
1360 +
1361 + // Exactly one initial layout calculation.
1362 + calculate(true);
944 944   }
945 945  
946 - schedule();
947 - setTimeout(schedule,200);
948 - setTimeout(schedule,650);
949 - setTimeout(schedule,1400);
1365 + addEventListener(
1366 + "resize",
1367 + resize,
1368 + {passive:true}
1369 + );
1370 +
1371 + initial();
950 950  })();
951 951  </script>
952 -<!-- SN_HORIZONTAL_BOX_HARMONIZER_END -->
953 -{{/html}}
954 954  
1375 +<!-- SN_HORIZONTAL_BOX_HARMONIZER_END -->
1376 +{{/html}}\n