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

From version 9.4
edited by Veronika Kocher
on Thursday, 03 Sep 2026 9:34
Change comment: There is no comment for this version
To version 9.3
edited by Veronika Kocher
on Thursday, 03 Sep 2026 9:18
Change comment: There is no comment for this version

Summary

Details

Page properties
Content
... ... @@ -427,22 +427,20 @@
427 427  
428 428  {{html clean="false"}}
429 429  <!-- SN_HORIZONTAL_BOX_HARMONIZER_START -->
430 -<style data-sn-horizontal-box-harmonizer="4">
431 -[data-sn-equal-height-managed],
432 -[data-sn-first-section-aligned]{
433 - box-sizing:border-box;
430 +<style data-sn-horizontal-box-harmonizer="1">
431 +[data-sn-equal-height-managed="1"]{box-sizing:border-box}
432 +@media(max-width:767px){
433 + [data-sn-equal-height-managed="1"]{min-height:0!important}
434 434  }
435 435  </style>
436 -<script data-sn-horizontal-box-harmonizer="4">
436 +<script data-sn-horizontal-box-harmonizer="1">
437 437  (function(){
438 438   "use strict";
439 439  
440 440   var MOBILE_MAX=767;
441 - var ROW_TOLERANCE=6;
441 + var ROW_TOLERANCE=4;
442 442   var MIN_W=150;
443 443   var MIN_H=36;
444 - var MAX_PARENT_CHILDREN=16;
445 - var SEARCH_DEPTH=5;
446 446  
447 447   var root=
448 448   document.getElementById("xwikicontent")||
... ... @@ -452,10 +452,7 @@
452 452  
453 453   if(!root)return;
454 454  
455 - var heightManaged=new Set();
456 - var sectionManaged=new Set();
457 - var originalMinHeight=new WeakMap();
458 - var originalMarginTop=new WeakMap();
453 + var managed=new Set();
459 459   var raf=0;
460 460  
461 461   function num(v){
... ... @@ -463,181 +463,64 @@
463 463   return Number.isFinite(n)?n:0;
464 464   }
465 465  
466 - function visible(el,minW,minH){
461 + function visible(el){
467 467   if(!el||el.nodeType!==1)return false;
468 -
469 469   var s=getComputedStyle(el);
470 -
471 - if(
472 - s.display==="none"||
473 - s.visibility==="hidden"||
474 - Number(s.opacity)===0
475 - ){
476 - return false;
477 - }
478 -
464 + if(s.display==="none"||s.visibility==="hidden"||Number(s.opacity)===0)return false;
479 479   var r=el.getBoundingClientRect();
480 -
481 - return (
482 - r.width>=(minW||1)&&
483 - r.height>=(minH||1)
484 - );
466 + return r.width>=MIN_W&&r.height>=MIN_H;
485 485   }
486 486  
487 487   function excluded(el){
488 - return (
489 - !el.matches||
490 - el.matches(
491 - "button,input,select,textarea,option,label,img,svg,canvas,table,thead,tbody,tfoot,tr,td,th,summary,script,style,noscript,template,.btn,.chip,[role=button]"
492 - )
470 + return !el.matches||el.matches(
471 + "button,input,select,textarea,option,label,img,svg,canvas,table,thead,tbody,tfoot,tr,td,th,summary,script,style,noscript,template,.btn,.chip,[role=button]"
493 493   );
494 494   }
495 495  
496 496   function namedLikeBox(el){
497 - var c=
498 - typeof el.className==="string"
499 - ? el.className
500 - : "";
501 -
502 - return /(^|[s_-])(box|card|tile|panel|role|check|service|source|call|area|detail|resource|topic|warn|info)([s_-]|$)/i.test(c);
476 + var c=typeof el.className==="string"?el.className:"";
477 + return /(^|[\s_-])(box|card|tile|panel|role|check|service|source|call|area|detail|resource|topic|warn|info)([\s_-]|$)/i.test(c);
503 503   }
504 504  
505 505   function boxLike(el){
506 - if(!visible(el,MIN_W,MIN_H)||excluded(el))return false;
507 -
481 + if(!visible(el)||excluded(el))return false;
508 508   var s=getComputedStyle(el);
509 509   var r=el.getBoundingClientRect();
510 -
511 511   var border=
512 - num(s.borderTopWidth)+
513 - num(s.borderRightWidth)+
514 - num(s.borderBottomWidth)+
515 - num(s.borderLeftWidth);
516 -
485 + num(s.borderTopWidth)+num(s.borderRightWidth)+
486 + num(s.borderBottomWidth)+num(s.borderLeftWidth);
517 517   var radius=Math.max(
518 - num(s.borderTopLeftRadius),
519 - num(s.borderTopRightRadius),
520 - num(s.borderBottomLeftRadius),
521 - num(s.borderBottomRightRadius)
488 + num(s.borderTopLeftRadius),num(s.borderTopRightRadius),
489 + num(s.borderBottomLeftRadius),num(s.borderBottomRightRadius)
522 522   );
523 -
524 - var bg=
525 - s.backgroundColor&&
526 - s.backgroundColor!=="rgba(0, 0, 0, 0)"&&
527 - s.backgroundColor!=="transparent";
528 -
529 - var shadow=
530 - s.boxShadow&&
531 - s.boxShadow!=="none";
532 -
533 - return (
534 - namedLikeBox(el)||
535 - border>=1||
536 - (bg&&radius>=3)||
537 - (shadow&&r.width>=180)
538 - );
491 + var bg=s.backgroundColor&&s.backgroundColor!=="rgba(0, 0, 0, 0)"&&s.backgroundColor!=="transparent";
492 + var shadow=s.boxShadow&&s.boxShadow!=="none";
493 + return namedLikeBox(el)||border>=1||(bg&&radius>=3)||(shadow&&r.width>=180);
539 539   }
540 540  
541 - function saveOriginal(el,map,property){
542 - if(map.has(el))return;
543 -
544 - map.set(el,{
545 - value:el.style.getPropertyValue(property),
546 - priority:el.style.getPropertyPriority(property)
547 - });
548 - }
549 -
550 - function restoreOriginal(el,map,property){
551 - var original=map.get(el);
552 -
553 - if(original&&original.value){
554 - el.style.setProperty(
555 - property,
556 - original.value,
557 - original.priority||""
558 - );
559 - }else{
560 - el.style.removeProperty(property);
561 - }
562 - }
563 -
564 - function reset(){
565 - heightManaged.forEach(function(el){
566 - restoreOriginal(
567 - el,
568 - originalMinHeight,
569 - "min-height"
570 - );
571 -
572 - el.removeAttribute(
573 - "data-sn-equal-height-managed"
574 - );
575 - });
576 -
577 - sectionManaged.forEach(function(el){
578 - restoreOriginal(
579 - el,
580 - originalMarginTop,
581 - "margin-top"
582 - );
583 -
584 - el.removeAttribute(
585 - "data-sn-first-section-aligned"
586 - );
587 - });
588 -
589 - heightManaged.clear();
590 - sectionManaged.clear();
591 - }
592 -
593 593   function pickTarget(child){
594 - if(!visible(child,MIN_W,MIN_H))return null;
497 + if(!visible(child))return null;
498 + if(boxLike(child))return child;
595 595  
596 - if(boxLike(child)){
597 - return child;
598 - }
599 -
600 600   var cr=child.getBoundingClientRect();
601 601   var q=[];
602 -
603 603   Array.from(child.children||[]).forEach(function(el){
604 604   q.push({el:el,depth:1});
605 605   });
606 606  
607 - var best=null;
608 - var bestScore=-Infinity;
506 + var best=null,bestScore=-1;
609 609  
610 610   while(q.length){
611 611   var item=q.shift();
612 612   var el=item.el;
511 + if(!visible(el))continue;
613 613  
614 - if(!visible(el,MIN_W,MIN_H))continue;
615 -
616 616   var r=el.getBoundingClientRect();
617 617  
618 - if(
619 - r.width>=cr.width*.68&&
620 - boxLike(el)
621 - ){
622 - var widthFit=
623 - 1-Math.min(
624 - 1,
625 - Math.abs(cr.width-r.width)/
626 - Math.max(1,cr.width)
627 - );
628 -
629 - var topFit=
630 - 1-Math.min(
631 - 1,
632 - Math.abs(cr.top-r.top)/100
633 - );
634 -
635 - var score=
636 - widthFit*6+
637 - topFit*3+
638 - Math.min(r.height,900)/450+
639 - Math.min(r.width*r.height,900000)/900000;
640 -
515 + if(r.width>=cr.width*.72&&boxLike(el)){
516 + var widthFit=1-Math.min(1,Math.abs(cr.width-r.width)/Math.max(1,cr.width));
517 + var topFit=1-Math.min(1,Math.abs(cr.top-r.top)/80);
518 + var score=widthFit*5+topFit*2+Math.min(r.height,600)/600;
641 641   if(score>bestScore){
642 642   best=el;
643 643   bestScore=score;
... ... @@ -644,12 +644,9 @@
644 644   }
645 645   }
646 646  
647 - if(item.depth<SEARCH_DEPTH){
525 + if(item.depth<3){
648 648   Array.from(el.children||[]).forEach(function(next){
649 - q.push({
650 - el:next,
651 - depth:item.depth+1
652 - });
527 + q.push({el:next,depth:item.depth+1});
653 653   });
654 654   }
655 655   }
... ... @@ -657,392 +657,61 @@
657 657   return best;
658 658   }
659 659  
660 - function groupsByVisualRow(entries){
661 - var groups=[];
662 -
663 - entries
664 - .slice()
665 - .sort(function(a,b){
666 - return (
667 - a.rect.top-b.rect.top||
668 - a.rect.left-b.rect.left
669 - );
670 - })
671 - .forEach(function(entry){
672 - var group=null;
673 -
674 - for(var i=0;i<groups.length;i++){
675 - if(
676 - Math.abs(
677 - groups[i].top-
678 - entry.rect.top
679 - )<=ROW_TOLERANCE
680 - ){
681 - group=groups[i];
682 - break;
683 - }
684 - }
685 -
686 - if(!group){
687 - group={
688 - top:entry.rect.top,
689 - entries:[]
690 - };
691 - groups.push(group);
692 - }
693 -
694 - group.entries.push(entry);
695 - });
696 -
697 - return groups;
535 + function reset(){
536 + managed.forEach(function(el){
537 + el.style.removeProperty("min-height");
538 + el.removeAttribute("data-sn-equal-height-managed");
539 + });
540 + managed.clear();
698 698   }
699 699  
700 - function validHorizontalGroup(group){
701 - if(group.entries.length<2)return false;
702 -
703 - var lefts=
704 - group.entries
705 - .map(function(e){return e.rect.left;})
706 - .sort(function(a,b){return a-b;});
707 -
708 - if(
709 - lefts[lefts.length-1]-
710 - lefts[0]<
711 - 80
712 - ){
713 - return false;
543 + function horizontalLayout(parent){
544 + if(!visible(parent))return false;
545 + var s=getComputedStyle(parent);
546 + if(s.display==="grid"||s.display==="inline-grid")return true;
547 + if(s.display==="flex"||s.display==="inline-flex"){
548 + return s.flexDirection==="row"||s.flexDirection==="row-reverse";
714 714   }
715 -
716 - var widths=
717 - group.entries
718 - .map(function(e){return e.rect.width;})
719 - .filter(function(w){return w>0;});
720 -
721 - if(widths.length<2)return false;
722 -
723 - var minWidth=Math.min.apply(Math,widths);
724 - var maxWidth=Math.max.apply(Math,widths);
725 -
726 - return (
727 - maxWidth/
728 - Math.max(1,minWidth)<=
729 - 1.9
730 - );
550 + return false;
731 731   }
732 732  
733 - function normalizeText(text){
734 - return String(text||"")
735 - .replace(/s+/g," ")
736 - .trim()
737 - .toUpperCase();
738 - }
739 -
740 - function exactKnownFirstSection(el){
741 - var text=normalizeText(el.textContent);
742 -
743 - return (
744 - text==="HIER STARTEN"||
745 - text==="ORGANISIEREN & DOKUMENTIEREN"||
746 - text==="ENTSCHEIDEN"||
747 - text==="START HERE"||
748 - text==="GET STARTED"||
749 - text==="ORGANISE & DOCUMENT"||
750 - text==="ORGANIZE & DOCUMENT"||
751 - text==="DECIDE"
752 - );
753 - }
754 -
755 - function visuallyFirstSectionBar(card){
756 - var cardRect=card.getBoundingClientRect();
757 - var candidates=[];
758 -
759 - Array.from(
760 - card.querySelectorAll(
761 - "div,p,span,strong,h3,h4,h5,h6"
762 - )
763 - ).forEach(function(el){
764 - if(!visible(el,20,6))return;
765 - if(excluded(el))return;
766 -
767 - var r=el.getBoundingClientRect();
768 - var relativeTop=r.top-cardRect.top;
769 -
770 - if(
771 - relativeTop<45||
772 - relativeTop>cardRect.height*.72
773 - ){
774 - return;
553 + function rowGroups(entries){
554 + var groups=[];
555 + entries.slice().sort(function(a,b){
556 + return a.rect.top-b.rect.top||a.rect.left-b.rect.left;
557 + }).forEach(function(entry){
558 + var group=null;
559 + for(var i=0;i<groups.length;i++){
560 + if(Math.abs(groups[i].top-entry.rect.top)<=ROW_TOLERANCE){
561 + group=groups[i];
562 + break;
563 + }
775 775   }
776 -
777 - var widthRatio=
778 - r.width/
779 - Math.max(1,cardRect.width);
780 -
781 - if(
782 - widthRatio<.55||
783 - widthRatio>1.05||
784 - r.height>54
785 - ){
786 - return;
565 + if(!group){
566 + group={top:entry.rect.top,entries:[]};
567 + groups.push(group);
787 787   }
788 -
789 - var text=normalizeText(el.textContent);
790 -
791 - if(
792 - text.length<2||
793 - text.length>90
794 - ){
795 - return;
796 - }
797 -
798 - var style=getComputedStyle(el);
799 -
800 - var bg=
801 - style.backgroundColor&&
802 - style.backgroundColor!=="rgba(0, 0, 0, 0)"&&
803 - style.backgroundColor!=="transparent";
804 -
805 - var border=
806 - num(style.borderTopWidth)+
807 - num(style.borderRightWidth)+
808 - num(style.borderBottomWidth)+
809 - num(style.borderLeftWidth);
810 -
811 - var mostlyUpper=
812 - text.length>=3&&
813 - text===text.toUpperCase();
814 -
815 - var exact=exactKnownFirstSection(el);
816 -
817 - var score=
818 - (exact?20:0)+
819 - (bg?6:0)+
820 - (border>=1?2:0)+
821 - (mostlyUpper?4:0)+
822 - (num(style.fontWeight)>=600?1:0);
823 -
824 - if(score>=7){
825 - candidates.push({
826 - el:el,
827 - relativeTop:relativeTop,
828 - width:r.width,
829 - score:score
830 - });
831 - }
569 + group.entries.push(entry);
832 832   });
833 -
834 - if(!candidates.length)return null;
835 -
836 - candidates.sort(function(a,b){
837 - if(a.score!==b.score)return b.score-a.score;
838 - if(Math.abs(a.relativeTop-b.relativeTop)>3){
839 - return a.relativeTop-b.relativeTop;
840 - }
841 - return b.width-a.width;
842 - });
843 -
844 - // For exact known labels always prefer the earliest exact match.
845 - var exacts=candidates.filter(function(c){
846 - return exactKnownFirstSection(c.el);
847 - });
848 -
849 - if(exacts.length){
850 - exacts.sort(function(a,b){
851 - return a.relativeTop-b.relativeTop||b.width-a.width;
852 - });
853 - return exacts[0].el;
854 - }
855 -
856 - // Generic fallback: earliest high-confidence bar.
857 - candidates.sort(function(a,b){
858 - return a.relativeTop-b.relativeTop||b.score-a.score||b.width-a.width;
859 - });
860 -
861 - return candidates[0].el;
571 + return groups;
862 862   }
863 863  
864 - function alignFirstSections(cards){
865 - var rows=
866 - cards.map(function(card){
867 - var bar=
868 - visuallyFirstSectionBar(card);
869 -
870 - if(!bar)return null;
871 -
872 - var cardRect=
873 - card.getBoundingClientRect();
874 -
875 - var barRect=
876 - bar.getBoundingClientRect();
877 -
878 - return {
879 - card:card,
880 - bar:bar,
881 - relativeTop:
882 - barRect.top-cardRect.top,
883 - label:
884 - normalizeText(bar.textContent)
885 - };
886 - });
887 -
888 - // Conservative: only align when every card in the horizontal row
889 - // has a reliable first section bar.
890 - if(
891 - rows.some(function(row){
892 - return !row;
893 - })
894 - ){
895 - return null;
896 - }
897 -
898 - var targetTop=
899 - Math.max.apply(
900 - Math,
901 - rows.map(function(row){
902 - return row.relativeTop;
903 - })
904 - );
905 -
906 - rows.forEach(function(row){
907 - var delta=
908 - targetTop-
909 - row.relativeTop;
910 -
911 - if(delta<=1)return;
912 -
913 - saveOriginal(
914 - row.bar,
915 - originalMarginTop,
916 - "margin-top"
917 - );
918 -
919 - var currentMargin=
920 - num(
921 - getComputedStyle(
922 - row.bar
923 - ).marginTop
924 - );
925 -
926 - row.bar.style.setProperty(
927 - "margin-top",
928 - (
929 - currentMargin+
930 - delta
931 - )+"px"
932 - );
933 -
934 - row.bar.setAttribute(
935 - "data-sn-first-section-aligned",
936 - "4"
937 - );
938 -
939 - sectionManaged.add(row.bar);
940 - });
941 -
942 - return {
943 - labels:
944 - rows.map(function(row){
945 - return row.label;
946 - }),
947 - targetRelativeTop:
948 - Math.round(targetTop)
949 - };
950 - }
951 -
952 - function applyGroup(group){
953 - if(!validHorizontalGroup(group)){
954 - return null;
955 - }
956 -
957 - var cards=[];
958 - var seen=new Set();
959 -
960 - group.entries.forEach(function(entry){
961 - if(!seen.has(entry.target)){
962 - seen.add(entry.target);
963 - cards.push(entry.target);
964 - }
965 - });
966 -
967 - if(cards.length<2)return null;
968 -
969 - var firstSection=
970 - alignFirstSections(cards);
971 -
972 - // The section alignment can change natural card heights.
973 - // Measure only afterwards.
974 - var maxHeight=0;
975 -
976 - cards.forEach(function(card){
977 - maxHeight=Math.max(
978 - maxHeight,
979 - card.getBoundingClientRect().height
980 - );
981 - });
982 -
983 - maxHeight=Math.ceil(maxHeight);
984 -
985 - if(maxHeight<MIN_H)return null;
986 -
987 - cards.forEach(function(card){
988 - saveOriginal(
989 - card,
990 - originalMinHeight,
991 - "min-height"
992 - );
993 -
994 - card.style.setProperty(
995 - "min-height",
996 - maxHeight+"px"
997 - );
998 -
999 - card.setAttribute(
1000 - "data-sn-equal-height-managed",
1001 - "4"
1002 - );
1003 -
1004 - heightManaged.add(card);
1005 - });
1006 -
1007 - return {
1008 - cards:cards.length,
1009 - height:maxHeight,
1010 - firstSection:firstSection
1011 - };
1012 - }
1013 -
1014 1014   function run(){
1015 1015   reset();
1016 1016  
1017 - if(
1018 - window.innerWidth<=MOBILE_MAX
1019 - ){
1020 - return;
1021 - }
577 + if(window.innerWidth<=MOBILE_MAX)return;
1022 1022  
1023 - var parents=
1024 - Array.from(
1025 - root.querySelectorAll("*")
1026 - ).filter(function(parent){
1027 - return (
1028 - visible(parent,MIN_W,MIN_H)&&
1029 - parent.children&&
1030 - parent.children.length>=2&&
1031 - parent.children.length<=MAX_PARENT_CHILDREN
1032 - );
1033 - });
579 + var parents=Array.from(root.querySelectorAll("*")).filter(function(el){
580 + return el.children&&el.children.length>=2&&horizontalLayout(el);
581 + });
1034 1034  
1035 - var touched=new Set();
1036 -
1037 1037   parents.forEach(function(parent){
1038 1038   var entries=[];
1039 1039  
1040 1040   Array.from(parent.children).forEach(function(child){
1041 - if(!visible(child,MIN_W,MIN_H))return;
1042 -
587 + if(!visible(child))return;
1043 1043   var target=pickTarget(child);
1044 1044   if(!target)return;
1045 -
1046 1046   entries.push({
1047 1047   target:target,
1048 1048   rect:target.getBoundingClientRect()
... ... @@ -1051,30 +1051,37 @@
1051 1051  
1052 1052   if(entries.length<2)return;
1053 1053  
1054 - groupsByVisualRow(entries).forEach(function(group){
1055 - if(!validHorizontalGroup(group))return;
598 + rowGroups(entries).forEach(function(group){
599 + if(group.entries.length<2)return;
1056 1056  
1057 - var signature=
1058 - group.entries.map(function(entry){
1059 - return entry.target;
1060 - });
601 + var lefts=group.entries.map(function(e){return e.rect.left;}).sort(function(a,b){return a-b;});
602 + if(lefts[lefts.length-1]-lefts[0]<80)return;
1061 1061  
1062 - if(
1063 - !signature.some(function(el){
1064 - return !touched.has(el);
1065 - })
1066 - ){
1067 - return;
1068 - }
604 + var targets=[];
605 + var seen=new Set();
1069 1069  
1070 - var applied=
1071 - applyGroup(group);
607 + group.entries.forEach(function(e){
608 + if(!seen.has(e.target)){
609 + seen.add(e.target);
610 + targets.push(e.target);
611 + }
612 + });
1072 1072  
1073 - if(applied){
1074 - signature.forEach(function(el){
1075 - touched.add(el);
1076 - });
1077 - }
614 + if(targets.length<2)return;
615 +
616 + var max=0;
617 + targets.forEach(function(el){
618 + max=Math.max(max,el.getBoundingClientRect().height);
619 + });
620 +
621 + max=Math.ceil(max);
622 + if(max<MIN_H)return;
623 +
624 + targets.forEach(function(el){
625 + el.style.setProperty("min-height",max+"px");
626 + el.setAttribute("data-sn-equal-height-managed","1");
627 + managed.add(el);
628 + });
1078 1078   });
1079 1079   });
1080 1080   }
... ... @@ -1081,7 +1081,6 @@
1081 1081  
1082 1082   function schedule(){
1083 1083   if(raf)cancelAnimationFrame(raf);
1084 -
1085 1085   raf=requestAnimationFrame(function(){
1086 1086   raf=requestAnimationFrame(function(){
1087 1087   raf=0;
... ... @@ -1093,28 +1093,17 @@
1093 1093   addEventListener("resize",schedule,{passive:true});
1094 1094   addEventListener("load",schedule,{once:true});
1095 1095  
1096 - root.addEventListener(
1097 - "click",
1098 - function(){
1099 - setTimeout(schedule,0);
1100 - },
1101 - true
1102 - );
646 + root.addEventListener("click",function(){
647 + setTimeout(schedule,0);
648 + },true);
1103 1103  
1104 - Array.from(root.querySelectorAll("img")).forEach(function(img){
1105 - if(!img.complete){
1106 - img.addEventListener("load",schedule,{once:true});
1107 - }
1108 - });
1109 -
1110 1110   if(document.fonts&&document.fonts.ready){
1111 1111   document.fonts.ready.then(schedule);
1112 1112   }
1113 1113  
1114 1114   schedule();
1115 - setTimeout(schedule,200);
1116 - setTimeout(schedule,650);
1117 - setTimeout(schedule,1400);
655 + setTimeout(schedule,250);
656 + setTimeout(schedule,900);
1118 1118  })();
1119 1119  </script>
1120 1120  <!-- SN_HORIZONTAL_BOX_HARMONIZER_END -->