Changes for page Service Navigator

Last modified by Veronika Kocher on Thursday, 03 Sep 2026 14:15

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