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

From version 5.9
edited by Veronika Kocher
on Thursday, 03 Sep 2026 9:34
Change comment: There is no comment for this version
To version 5.8
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
... ... @@ -263,22 +263,20 @@
263 263  
264 264  {{html clean="false"}}
265 265  <!-- SN_HORIZONTAL_BOX_HARMONIZER_START -->
266 -<style data-sn-horizontal-box-harmonizer="4">
267 -[data-sn-equal-height-managed],
268 -[data-sn-first-section-aligned]{
269 - box-sizing:border-box;
266 +<style data-sn-horizontal-box-harmonizer="1">
267 +[data-sn-equal-height-managed="1"]{box-sizing:border-box}
268 +@media(max-width:767px){
269 + [data-sn-equal-height-managed="1"]{min-height:0!important}
270 270  }
271 271  </style>
272 -<script data-sn-horizontal-box-harmonizer="4">
272 +<script data-sn-horizontal-box-harmonizer="1">
273 273  (function(){
274 274   "use strict";
275 275  
276 276   var MOBILE_MAX=767;
277 - var ROW_TOLERANCE=6;
277 + var ROW_TOLERANCE=4;
278 278   var MIN_W=150;
279 279   var MIN_H=36;
280 - var MAX_PARENT_CHILDREN=16;
281 - var SEARCH_DEPTH=5;
282 282  
283 283   var root=
284 284   document.getElementById("xwikicontent")||
... ... @@ -288,10 +288,7 @@
288 288  
289 289   if(!root)return;
290 290  
291 - var heightManaged=new Set();
292 - var sectionManaged=new Set();
293 - var originalMinHeight=new WeakMap();
294 - var originalMarginTop=new WeakMap();
289 + var managed=new Set();
295 295   var raf=0;
296 296  
297 297   function num(v){
... ... @@ -299,181 +299,64 @@
299 299   return Number.isFinite(n)?n:0;
300 300   }
301 301  
302 - function visible(el,minW,minH){
297 + function visible(el){
303 303   if(!el||el.nodeType!==1)return false;
304 -
305 305   var s=getComputedStyle(el);
306 -
307 - if(
308 - s.display==="none"||
309 - s.visibility==="hidden"||
310 - Number(s.opacity)===0
311 - ){
312 - return false;
313 - }
314 -
300 + if(s.display==="none"||s.visibility==="hidden"||Number(s.opacity)===0)return false;
315 315   var r=el.getBoundingClientRect();
316 -
317 - return (
318 - r.width>=(minW||1)&&
319 - r.height>=(minH||1)
320 - );
302 + return r.width>=MIN_W&&r.height>=MIN_H;
321 321   }
322 322  
323 323   function excluded(el){
324 - return (
325 - !el.matches||
326 - el.matches(
327 - "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]"
328 - )
306 + return !el.matches||el.matches(
307 + "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]"
329 329   );
330 330   }
331 331  
332 332   function namedLikeBox(el){
333 - var c=
334 - typeof el.className==="string"
335 - ? el.className
336 - : "";
337 -
338 - return /(^|[s_-])(box|card|tile|panel|role|check|service|source|call|area|detail|resource|topic|warn|info)([s_-]|$)/i.test(c);
312 + var c=typeof el.className==="string"?el.className:"";
313 + return /(^|[\s_-])(box|card|tile|panel|role|check|service|source|call|area|detail|resource|topic|warn|info)([\s_-]|$)/i.test(c);
339 339   }
340 340  
341 341   function boxLike(el){
342 - if(!visible(el,MIN_W,MIN_H)||excluded(el))return false;
343 -
317 + if(!visible(el)||excluded(el))return false;
344 344   var s=getComputedStyle(el);
345 345   var r=el.getBoundingClientRect();
346 -
347 347   var border=
348 - num(s.borderTopWidth)+
349 - num(s.borderRightWidth)+
350 - num(s.borderBottomWidth)+
351 - num(s.borderLeftWidth);
352 -
321 + num(s.borderTopWidth)+num(s.borderRightWidth)+
322 + num(s.borderBottomWidth)+num(s.borderLeftWidth);
353 353   var radius=Math.max(
354 - num(s.borderTopLeftRadius),
355 - num(s.borderTopRightRadius),
356 - num(s.borderBottomLeftRadius),
357 - num(s.borderBottomRightRadius)
324 + num(s.borderTopLeftRadius),num(s.borderTopRightRadius),
325 + num(s.borderBottomLeftRadius),num(s.borderBottomRightRadius)
358 358   );
359 -
360 - var bg=
361 - s.backgroundColor&&
362 - s.backgroundColor!=="rgba(0, 0, 0, 0)"&&
363 - s.backgroundColor!=="transparent";
364 -
365 - var shadow=
366 - s.boxShadow&&
367 - s.boxShadow!=="none";
368 -
369 - return (
370 - namedLikeBox(el)||
371 - border>=1||
372 - (bg&&radius>=3)||
373 - (shadow&&r.width>=180)
374 - );
327 + var bg=s.backgroundColor&&s.backgroundColor!=="rgba(0, 0, 0, 0)"&&s.backgroundColor!=="transparent";
328 + var shadow=s.boxShadow&&s.boxShadow!=="none";
329 + return namedLikeBox(el)||border>=1||(bg&&radius>=3)||(shadow&&r.width>=180);
375 375   }
376 376  
377 - function saveOriginal(el,map,property){
378 - if(map.has(el))return;
379 -
380 - map.set(el,{
381 - value:el.style.getPropertyValue(property),
382 - priority:el.style.getPropertyPriority(property)
383 - });
384 - }
385 -
386 - function restoreOriginal(el,map,property){
387 - var original=map.get(el);
388 -
389 - if(original&&original.value){
390 - el.style.setProperty(
391 - property,
392 - original.value,
393 - original.priority||""
394 - );
395 - }else{
396 - el.style.removeProperty(property);
397 - }
398 - }
399 -
400 - function reset(){
401 - heightManaged.forEach(function(el){
402 - restoreOriginal(
403 - el,
404 - originalMinHeight,
405 - "min-height"
406 - );
407 -
408 - el.removeAttribute(
409 - "data-sn-equal-height-managed"
410 - );
411 - });
412 -
413 - sectionManaged.forEach(function(el){
414 - restoreOriginal(
415 - el,
416 - originalMarginTop,
417 - "margin-top"
418 - );
419 -
420 - el.removeAttribute(
421 - "data-sn-first-section-aligned"
422 - );
423 - });
424 -
425 - heightManaged.clear();
426 - sectionManaged.clear();
427 - }
428 -
429 429   function pickTarget(child){
430 - if(!visible(child,MIN_W,MIN_H))return null;
333 + if(!visible(child))return null;
334 + if(boxLike(child))return child;
431 431  
432 - if(boxLike(child)){
433 - return child;
434 - }
435 -
436 436   var cr=child.getBoundingClientRect();
437 437   var q=[];
438 -
439 439   Array.from(child.children||[]).forEach(function(el){
440 440   q.push({el:el,depth:1});
441 441   });
442 442  
443 - var best=null;
444 - var bestScore=-Infinity;
342 + var best=null,bestScore=-1;
445 445  
446 446   while(q.length){
447 447   var item=q.shift();
448 448   var el=item.el;
347 + if(!visible(el))continue;
449 449  
450 - if(!visible(el,MIN_W,MIN_H))continue;
451 -
452 452   var r=el.getBoundingClientRect();
453 453  
454 - if(
455 - r.width>=cr.width*.68&&
456 - boxLike(el)
457 - ){
458 - var widthFit=
459 - 1-Math.min(
460 - 1,
461 - Math.abs(cr.width-r.width)/
462 - Math.max(1,cr.width)
463 - );
464 -
465 - var topFit=
466 - 1-Math.min(
467 - 1,
468 - Math.abs(cr.top-r.top)/100
469 - );
470 -
471 - var score=
472 - widthFit*6+
473 - topFit*3+
474 - Math.min(r.height,900)/450+
475 - Math.min(r.width*r.height,900000)/900000;
476 -
351 + if(r.width>=cr.width*.72&&boxLike(el)){
352 + var widthFit=1-Math.min(1,Math.abs(cr.width-r.width)/Math.max(1,cr.width));
353 + var topFit=1-Math.min(1,Math.abs(cr.top-r.top)/80);
354 + var score=widthFit*5+topFit*2+Math.min(r.height,600)/600;
477 477   if(score>bestScore){
478 478   best=el;
479 479   bestScore=score;
... ... @@ -480,12 +480,9 @@
480 480   }
481 481   }
482 482  
483 - if(item.depth<SEARCH_DEPTH){
361 + if(item.depth<3){
484 484   Array.from(el.children||[]).forEach(function(next){
485 - q.push({
486 - el:next,
487 - depth:item.depth+1
488 - });
363 + q.push({el:next,depth:item.depth+1});
489 489   });
490 490   }
491 491   }
... ... @@ -493,392 +493,61 @@
493 493   return best;
494 494   }
495 495  
496 - function groupsByVisualRow(entries){
497 - var groups=[];
498 -
499 - entries
500 - .slice()
501 - .sort(function(a,b){
502 - return (
503 - a.rect.top-b.rect.top||
504 - a.rect.left-b.rect.left
505 - );
506 - })
507 - .forEach(function(entry){
508 - var group=null;
509 -
510 - for(var i=0;i<groups.length;i++){
511 - if(
512 - Math.abs(
513 - groups[i].top-
514 - entry.rect.top
515 - )<=ROW_TOLERANCE
516 - ){
517 - group=groups[i];
518 - break;
519 - }
520 - }
521 -
522 - if(!group){
523 - group={
524 - top:entry.rect.top,
525 - entries:[]
526 - };
527 - groups.push(group);
528 - }
529 -
530 - group.entries.push(entry);
531 - });
532 -
533 - return groups;
371 + function reset(){
372 + managed.forEach(function(el){
373 + el.style.removeProperty("min-height");
374 + el.removeAttribute("data-sn-equal-height-managed");
375 + });
376 + managed.clear();
534 534   }
535 535  
536 - function validHorizontalGroup(group){
537 - if(group.entries.length<2)return false;
538 -
539 - var lefts=
540 - group.entries
541 - .map(function(e){return e.rect.left;})
542 - .sort(function(a,b){return a-b;});
543 -
544 - if(
545 - lefts[lefts.length-1]-
546 - lefts[0]<
547 - 80
548 - ){
549 - return false;
379 + function horizontalLayout(parent){
380 + if(!visible(parent))return false;
381 + var s=getComputedStyle(parent);
382 + if(s.display==="grid"||s.display==="inline-grid")return true;
383 + if(s.display==="flex"||s.display==="inline-flex"){
384 + return s.flexDirection==="row"||s.flexDirection==="row-reverse";
550 550   }
551 -
552 - var widths=
553 - group.entries
554 - .map(function(e){return e.rect.width;})
555 - .filter(function(w){return w>0;});
556 -
557 - if(widths.length<2)return false;
558 -
559 - var minWidth=Math.min.apply(Math,widths);
560 - var maxWidth=Math.max.apply(Math,widths);
561 -
562 - return (
563 - maxWidth/
564 - Math.max(1,minWidth)<=
565 - 1.9
566 - );
386 + return false;
567 567   }
568 568  
569 - function normalizeText(text){
570 - return String(text||"")
571 - .replace(/s+/g," ")
572 - .trim()
573 - .toUpperCase();
574 - }
575 -
576 - function exactKnownFirstSection(el){
577 - var text=normalizeText(el.textContent);
578 -
579 - return (
580 - text==="HIER STARTEN"||
581 - text==="ORGANISIEREN & DOKUMENTIEREN"||
582 - text==="ENTSCHEIDEN"||
583 - text==="START HERE"||
584 - text==="GET STARTED"||
585 - text==="ORGANISE & DOCUMENT"||
586 - text==="ORGANIZE & DOCUMENT"||
587 - text==="DECIDE"
588 - );
589 - }
590 -
591 - function visuallyFirstSectionBar(card){
592 - var cardRect=card.getBoundingClientRect();
593 - var candidates=[];
594 -
595 - Array.from(
596 - card.querySelectorAll(
597 - "div,p,span,strong,h3,h4,h5,h6"
598 - )
599 - ).forEach(function(el){
600 - if(!visible(el,20,6))return;
601 - if(excluded(el))return;
602 -
603 - var r=el.getBoundingClientRect();
604 - var relativeTop=r.top-cardRect.top;
605 -
606 - if(
607 - relativeTop<45||
608 - relativeTop>cardRect.height*.72
609 - ){
610 - return;
389 + function rowGroups(entries){
390 + var groups=[];
391 + entries.slice().sort(function(a,b){
392 + return a.rect.top-b.rect.top||a.rect.left-b.rect.left;
393 + }).forEach(function(entry){
394 + var group=null;
395 + for(var i=0;i<groups.length;i++){
396 + if(Math.abs(groups[i].top-entry.rect.top)<=ROW_TOLERANCE){
397 + group=groups[i];
398 + break;
399 + }
611 611   }
612 -
613 - var widthRatio=
614 - r.width/
615 - Math.max(1,cardRect.width);
616 -
617 - if(
618 - widthRatio<.55||
619 - widthRatio>1.05||
620 - r.height>54
621 - ){
622 - return;
401 + if(!group){
402 + group={top:entry.rect.top,entries:[]};
403 + groups.push(group);
623 623   }
624 -
625 - var text=normalizeText(el.textContent);
626 -
627 - if(
628 - text.length<2||
629 - text.length>90
630 - ){
631 - return;
632 - }
633 -
634 - var style=getComputedStyle(el);
635 -
636 - var bg=
637 - style.backgroundColor&&
638 - style.backgroundColor!=="rgba(0, 0, 0, 0)"&&
639 - style.backgroundColor!=="transparent";
640 -
641 - var border=
642 - num(style.borderTopWidth)+
643 - num(style.borderRightWidth)+
644 - num(style.borderBottomWidth)+
645 - num(style.borderLeftWidth);
646 -
647 - var mostlyUpper=
648 - text.length>=3&&
649 - text===text.toUpperCase();
650 -
651 - var exact=exactKnownFirstSection(el);
652 -
653 - var score=
654 - (exact?20:0)+
655 - (bg?6:0)+
656 - (border>=1?2:0)+
657 - (mostlyUpper?4:0)+
658 - (num(style.fontWeight)>=600?1:0);
659 -
660 - if(score>=7){
661 - candidates.push({
662 - el:el,
663 - relativeTop:relativeTop,
664 - width:r.width,
665 - score:score
666 - });
667 - }
405 + group.entries.push(entry);
668 668   });
669 -
670 - if(!candidates.length)return null;
671 -
672 - candidates.sort(function(a,b){
673 - if(a.score!==b.score)return b.score-a.score;
674 - if(Math.abs(a.relativeTop-b.relativeTop)>3){
675 - return a.relativeTop-b.relativeTop;
676 - }
677 - return b.width-a.width;
678 - });
679 -
680 - // For exact known labels always prefer the earliest exact match.
681 - var exacts=candidates.filter(function(c){
682 - return exactKnownFirstSection(c.el);
683 - });
684 -
685 - if(exacts.length){
686 - exacts.sort(function(a,b){
687 - return a.relativeTop-b.relativeTop||b.width-a.width;
688 - });
689 - return exacts[0].el;
690 - }
691 -
692 - // Generic fallback: earliest high-confidence bar.
693 - candidates.sort(function(a,b){
694 - return a.relativeTop-b.relativeTop||b.score-a.score||b.width-a.width;
695 - });
696 -
697 - return candidates[0].el;
407 + return groups;
698 698   }
699 699  
700 - function alignFirstSections(cards){
701 - var rows=
702 - cards.map(function(card){
703 - var bar=
704 - visuallyFirstSectionBar(card);
705 -
706 - if(!bar)return null;
707 -
708 - var cardRect=
709 - card.getBoundingClientRect();
710 -
711 - var barRect=
712 - bar.getBoundingClientRect();
713 -
714 - return {
715 - card:card,
716 - bar:bar,
717 - relativeTop:
718 - barRect.top-cardRect.top,
719 - label:
720 - normalizeText(bar.textContent)
721 - };
722 - });
723 -
724 - // Conservative: only align when every card in the horizontal row
725 - // has a reliable first section bar.
726 - if(
727 - rows.some(function(row){
728 - return !row;
729 - })
730 - ){
731 - return null;
732 - }
733 -
734 - var targetTop=
735 - Math.max.apply(
736 - Math,
737 - rows.map(function(row){
738 - return row.relativeTop;
739 - })
740 - );
741 -
742 - rows.forEach(function(row){
743 - var delta=
744 - targetTop-
745 - row.relativeTop;
746 -
747 - if(delta<=1)return;
748 -
749 - saveOriginal(
750 - row.bar,
751 - originalMarginTop,
752 - "margin-top"
753 - );
754 -
755 - var currentMargin=
756 - num(
757 - getComputedStyle(
758 - row.bar
759 - ).marginTop
760 - );
761 -
762 - row.bar.style.setProperty(
763 - "margin-top",
764 - (
765 - currentMargin+
766 - delta
767 - )+"px"
768 - );
769 -
770 - row.bar.setAttribute(
771 - "data-sn-first-section-aligned",
772 - "4"
773 - );
774 -
775 - sectionManaged.add(row.bar);
776 - });
777 -
778 - return {
779 - labels:
780 - rows.map(function(row){
781 - return row.label;
782 - }),
783 - targetRelativeTop:
784 - Math.round(targetTop)
785 - };
786 - }
787 -
788 - function applyGroup(group){
789 - if(!validHorizontalGroup(group)){
790 - return null;
791 - }
792 -
793 - var cards=[];
794 - var seen=new Set();
795 -
796 - group.entries.forEach(function(entry){
797 - if(!seen.has(entry.target)){
798 - seen.add(entry.target);
799 - cards.push(entry.target);
800 - }
801 - });
802 -
803 - if(cards.length<2)return null;
804 -
805 - var firstSection=
806 - alignFirstSections(cards);
807 -
808 - // The section alignment can change natural card heights.
809 - // Measure only afterwards.
810 - var maxHeight=0;
811 -
812 - cards.forEach(function(card){
813 - maxHeight=Math.max(
814 - maxHeight,
815 - card.getBoundingClientRect().height
816 - );
817 - });
818 -
819 - maxHeight=Math.ceil(maxHeight);
820 -
821 - if(maxHeight<MIN_H)return null;
822 -
823 - cards.forEach(function(card){
824 - saveOriginal(
825 - card,
826 - originalMinHeight,
827 - "min-height"
828 - );
829 -
830 - card.style.setProperty(
831 - "min-height",
832 - maxHeight+"px"
833 - );
834 -
835 - card.setAttribute(
836 - "data-sn-equal-height-managed",
837 - "4"
838 - );
839 -
840 - heightManaged.add(card);
841 - });
842 -
843 - return {
844 - cards:cards.length,
845 - height:maxHeight,
846 - firstSection:firstSection
847 - };
848 - }
849 -
850 850   function run(){
851 851   reset();
852 852  
853 - if(
854 - window.innerWidth<=MOBILE_MAX
855 - ){
856 - return;
857 - }
413 + if(window.innerWidth<=MOBILE_MAX)return;
858 858  
859 - var parents=
860 - Array.from(
861 - root.querySelectorAll("*")
862 - ).filter(function(parent){
863 - return (
864 - visible(parent,MIN_W,MIN_H)&&
865 - parent.children&&
866 - parent.children.length>=2&&
867 - parent.children.length<=MAX_PARENT_CHILDREN
868 - );
869 - });
415 + var parents=Array.from(root.querySelectorAll("*")).filter(function(el){
416 + return el.children&&el.children.length>=2&&horizontalLayout(el);
417 + });
870 870  
871 - var touched=new Set();
872 -
873 873   parents.forEach(function(parent){
874 874   var entries=[];
875 875  
876 876   Array.from(parent.children).forEach(function(child){
877 - if(!visible(child,MIN_W,MIN_H))return;
878 -
423 + if(!visible(child))return;
879 879   var target=pickTarget(child);
880 880   if(!target)return;
881 -
882 882   entries.push({
883 883   target:target,
884 884   rect:target.getBoundingClientRect()
... ... @@ -887,30 +887,37 @@
887 887  
888 888   if(entries.length<2)return;
889 889  
890 - groupsByVisualRow(entries).forEach(function(group){
891 - if(!validHorizontalGroup(group))return;
434 + rowGroups(entries).forEach(function(group){
435 + if(group.entries.length<2)return;
892 892  
893 - var signature=
894 - group.entries.map(function(entry){
895 - return entry.target;
896 - });
437 + var lefts=group.entries.map(function(e){return e.rect.left;}).sort(function(a,b){return a-b;});
438 + if(lefts[lefts.length-1]-lefts[0]<80)return;
897 897  
898 - if(
899 - !signature.some(function(el){
900 - return !touched.has(el);
901 - })
902 - ){
903 - return;
904 - }
440 + var targets=[];
441 + var seen=new Set();
905 905  
906 - var applied=
907 - applyGroup(group);
443 + group.entries.forEach(function(e){
444 + if(!seen.has(e.target)){
445 + seen.add(e.target);
446 + targets.push(e.target);
447 + }
448 + });
908 908  
909 - if(applied){
910 - signature.forEach(function(el){
911 - touched.add(el);
912 - });
913 - }
450 + if(targets.length<2)return;
451 +
452 + var max=0;
453 + targets.forEach(function(el){
454 + max=Math.max(max,el.getBoundingClientRect().height);
455 + });
456 +
457 + max=Math.ceil(max);
458 + if(max<MIN_H)return;
459 +
460 + targets.forEach(function(el){
461 + el.style.setProperty("min-height",max+"px");
462 + el.setAttribute("data-sn-equal-height-managed","1");
463 + managed.add(el);
464 + });
914 914   });
915 915   });
916 916   }
... ... @@ -917,7 +917,6 @@
917 917  
918 918   function schedule(){
919 919   if(raf)cancelAnimationFrame(raf);
920 -
921 921   raf=requestAnimationFrame(function(){
922 922   raf=requestAnimationFrame(function(){
923 923   raf=0;
... ... @@ -929,28 +929,17 @@
929 929   addEventListener("resize",schedule,{passive:true});
930 930   addEventListener("load",schedule,{once:true});
931 931  
932 - root.addEventListener(
933 - "click",
934 - function(){
935 - setTimeout(schedule,0);
936 - },
937 - true
938 - );
482 + root.addEventListener("click",function(){
483 + setTimeout(schedule,0);
484 + },true);
939 939  
940 - Array.from(root.querySelectorAll("img")).forEach(function(img){
941 - if(!img.complete){
942 - img.addEventListener("load",schedule,{once:true});
943 - }
944 - });
945 -
946 946   if(document.fonts&&document.fonts.ready){
947 947   document.fonts.ready.then(schedule);
948 948   }
949 949  
950 950   schedule();
951 - setTimeout(schedule,200);
952 - setTimeout(schedule,650);
953 - setTimeout(schedule,1400);
491 + setTimeout(schedule,250);
492 + setTimeout(schedule,900);
954 954  })();
955 955  </script>
956 956  <!-- SN_HORIZONTAL_BOX_HARMONIZER_END -->