MediaWiki:Common.js: Unterschied zwischen den Versionen

Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
 
(59 dazwischenliegende Versionen von 2 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
/* === PREPRINT START === */
/* === PREPRINT START === */
/* ============================================
/* ============================================
   PREPRINT v7.17 - JS v260211r1
   PREPRINT v7.17 - JS v260225r1
 
  ARCHITECTURE:
  - Platform Adaptation Layer (PAL): isolates Minerva workarounds
  - Model Layer: dimensional intent, linkage fabric, projection
  - Feature Layer: BookNav, TOC, Snippets
   ============================================ */
   ============================================ */


Zeile 14: Zeile 9:
     // Prevent repeated initialization
     // Prevent repeated initialization
     if (window.preprintInitialized) {
     if (window.preprintInitialized) {
        console.log('Preprint: Already initialized, skipping');
         return;
         return;
     }
     }
Zeile 35: Zeile 29:
         var imagesStabilized = false;
         var imagesStabilized = false;
          
          
         // Debug logging with source tracking
         // Debug logging with source tracking (silent in production)
         function log(adapter, message) {
         function log(adapter, message) {
            console.log('Preprint PAL.' + adapter + ': ' + message);
         }
         }


Zeile 52: Zeile 45:
                 }
                 }
                  
                  
                 // Minerva: poll for section wrappers and unwrap them
                 // Minerva: observe mutations, unwrap sections as they appear
                 var attempts = 0;
                 var debounceTimer = null;
                 var maxAttempts = 20; // 20 * 50ms = 1000ms max
                 var timeoutTimer = null;
                 var lastSectionCount = -1;
                var observer = null;
               
                function cleanup() {
                    if (observer) observer.disconnect();
                    clearTimeout(debounceTimer);
                    clearTimeout(timeoutTimer);
                }
               
                 function done(reason) {
                    cleanup();
                    disableMinervaCollapse();
                    domStabilized = true;
                    log('DOM', reason);
                    resolve();
                }
                  
                  
                 function poll() {
                 function unwrapSections() {
                    attempts++;
                     var sections = $('.mw-parser-output > section');
                     var sections = $('.mw-parser-output > section');
                     var currentCount = sections.length;
                     if (sections.length > 0) {
                   
                    if (currentCount > 0) {
                        // Unwrap sections
                         sections.each(function() {
                         sections.each(function() {
                             $(this).replaceWith($(this).contents());
                             $(this).replaceWith($(this).contents());
                         });
                         });
                         log('DOM', 'Unwrapped ' + currentCount + ' sections');
                         log('DOM', 'Unwrapped ' + sections.length + ' sections');
                         lastSectionCount = currentCount;
                         return true;
                       
                        // Continue polling - more may appear
                        if (attempts < maxAttempts) {
                            setTimeout(poll, 50);
                            return;
                        }
                    }
                   
                    if (currentCount === 0 && lastSectionCount === 0) {
                        // Stable: no sections for two consecutive polls
                        disableMinervaCollapse();
                        domStabilized = true;
                        log('DOM', 'Stable after ' + attempts + ' polls');
                        resolve();
                    } else if (attempts >= maxAttempts) {
                        // Timeout fallback
                        disableMinervaCollapse();
                        domStabilized = true;
                        log('DOM', 'Timeout after ' + attempts + ' polls (fallback)');
                        resolve();
                    } else {
                        lastSectionCount = currentCount;
                        setTimeout(poll, 50);
                     }
                     }
                    return false;
                }
               
                function resetDebounce() {
                    clearTimeout(debounceTimer);
                    debounceTimer = setTimeout(function() {
                        done('Stable (100ms silence)');
                    }, 100);
                 }
                 }
                  
                  
                 // Start polling after initial delay for Minerva to begin its work
                var parserOutput = document.querySelector('.mw-parser-output');
                 setTimeout(poll, 100);
                if (!parserOutput) {
             });
                    done('No .mw-parser-output found');
         }
                    return;
          
                }
               
                observer = new MutationObserver(function() {
                    unwrapSections();
                    resetDebounce();
                });
                observer.observe(parserOutput, { childList: true, subtree: true });
               
                // Unwrap any sections already present
                unwrapSections();
               
                 // Start silence timer
                 resetDebounce();
               
                // Timeout fallback: 1000ms
                timeoutTimer = setTimeout(function() {
                    done('Timeout (1000ms fallback)');
                }, 1000);
             });
         }
          
         function disableMinervaCollapse() {
         function disableMinervaCollapse() {
             function sweep() {
             function sweep() {
Zeile 277: Zeile 285:
              
              
             var resizeTimer = null;
             var resizeTimer = null;
            var transitionGeneration = 0;
           
            var resizeTimer = null;
            var transitionGeneration = 0;
              
              
             $(window).on('resize orientationchange', function() {
             $(window).on('resize orientationchange', function() {
Zeile 282: Zeile 294:
                  
                  
                 if (targetOrientation === currentOrientation) {
                 if (targetOrientation === currentOrientation) {
                     // Cancel any in-flight transition that is now obsolete
                     // Cancel any in-flight transition (timer or promise)
                     if (orientationLocked) {
                     if (orientationLocked) {
                         clearTimeout(resizeTimer);
                         clearTimeout(resizeTimer);
                        transitionGeneration++;
                         setOrientationClass(currentOrientation);
                         setOrientationClass(currentOrientation);
                         orientationLocked = false;
                         orientationLocked = false;
                        // Restore layout visibility (pre-change hid it)
                        var el = document.querySelector('.preprint-layout');
                        if (el) { el.style.transition = 'opacity 0.15s'; el.style.opacity = '1'; }
                         log('Orientation', 'Cancelled pending transition, staying ' + currentOrientation);
                         log('Orientation', 'Cancelled pending transition, staying ' + currentOrientation);
                     }
                     }
Zeile 301: Zeile 317:
                 });
                 });
                  
                  
                 // Lock during transition
                 // Lock during transition, invalidate any in-flight promise
                 orientationLocked = true;
                 orientationLocked = true;
                clearTimeout(resizeTimer);
                var myGeneration = ++transitionGeneration;
                  
                  
                clearTimeout(resizeTimer);
                 resizeTimer = setTimeout(function() {
                 resizeTimer = setTimeout(function() {
                     setOrientationClass(targetOrientation);
                     setOrientationClass(targetOrientation);
                      
                      
                     waitForLayoutStability(targetOrientation).then(function() {
                     // Force-load images if going to landscape and not yet loaded
                    var imagePromise = (targetOrientation === 'landscape' && !imagesStabilized)
                        ? ensureImageStability()
                        : Promise.resolve();
                   
                    Promise.all([waitForLayoutStability(targetOrientation), imagePromise]).then(function() {
                        // Stale transition: a newer one superseded us
                        if (myGeneration !== transitionGeneration) {
                            log('Orientation', 'Dropped stale transition (gen ' + myGeneration + ', current ' + transitionGeneration + ')');
                            return;
                        }
                       
                         var previousOrientation = currentOrientation;
                         var previousOrientation = currentOrientation;
                         currentOrientation = targetOrientation;
                         currentOrientation = targetOrientation;
Zeile 329: Zeile 357:
                 }, 150);
                 }, 150);
             });
             });
             // Wake-detect: device sleep doesn't fire resize/orientationchange
             // Wake-detect: device sleep doesn't fire resize/orientationchange
             document.addEventListener('visibilitychange', function() {
             document.addEventListener('visibilitychange', function() {
Zeile 334: Zeile 363:
                 var targetOrientation = isNarrow() ? 'portrait' : 'landscape';
                 var targetOrientation = isNarrow() ? 'portrait' : 'landscape';
                 if (targetOrientation !== currentOrientation) {
                 if (targetOrientation !== currentOrientation) {
                     log('Orientation', 'Wake: ' + currentOrientation + ' → ' + targetOrientation);
                     log('Orientation', 'Wake: ' + currentOrientation + ' -> ' + targetOrientation);
                     $(window).trigger('resize');
                     $(window).trigger('resize');
                 }
                 }
Zeile 436: Zeile 465:
                     waitForDOMStability()
                     waitForDOMStability()
                         .then(function() {
                         .then(function() {
                             return ensureImageStability();
                             if (isNarrow()) {
                        })
                                // Portrait: TOC only, images load progressively
                        .then(function() {
                                return waitForTOCStability();
                            return waitForTOCStability();
                            }
                            // Landscape: images + TOC in parallel
                            return Promise.all([ensureImageStability(), waitForTOCStability()]);
                         })
                         })
                         .then(function() {
                         .then(function() {
                             initOrientationHandler();
                             initOrientationHandler();
                             log('ready', 'PAL ready (Minerva=' + isMinerva + ')');
                             log('ready', 'PAL ready (Minerva=' + isMinerva + ')');
Zeile 473: Zeile 505:
        
        
       - Dimensional Intent: IC, OC, LH, 2C (from markup)
       - Dimensional Intent: IC, OC, LH, 2C (from markup)
       - Linkage Fabric: markers ↔ elements (bidirectional)
       - Linkage Fabric: markers <-> elements (bidirectional)
       - Projection: landscape grid | portrait flow
       - Projection: landscape grid | portrait flow
       - User State: element-relative position tracking
       - User State: element-relative position tracking
Zeile 534: Zeile 566:
         }
         }


         /* --- Structure Building --- */
         /* --- List Repair --- */
          
          
         function buildStructure() {
        // MW parser ejects template-generated spans (Place) from list items:
             var parserOutput = $('.mw-parser-output');
        //  Pattern A (start): <li class="mw-empty-elt"/> + <p><span>...</span>text</p>
             var elements = parserOutput.children().toArray();
        //  Pattern B (end):  <li>text</li> + <p><span>...</span></p>
        // Both followed optionally by a continuation list.
        // This repairs the DOM before structure analysis.
         function repairListPlacement() {
             var $output = $('.mw-parser-output');
             if (!$output.length) return;
            var repaired = 0;
              
              
             layout = $('<div class="preprint-layout"></div>');
             function findEmptyItem($list) {
            var currentInner = null;
                var $li = $list.find('li.mw-empty-elt').last();
             var currentOuter = null;
                if ($li.length) return $li;
            var pendingOC = [];
                var $dd = $list.find('dd').filter(function() {
            var bookNavElements = [];
                    return this.childNodes.length === 0;
                }).last();
                return $dd.length ? $dd : null;
             }
           
            function isNestingWrapper(el) {
                if (el.tagName !== 'LI') return false;
                if (el.children.length !== 1) return false;
                if (!/^(UL|OL|DL)$/.test(el.firstElementChild.tagName)) return false;
                for (var i = 0; i < el.childNodes.length; i++) {
                    if (el.childNodes[i].nodeType === 3 && el.childNodes[i].textContent.trim()) return false;
                }
                return true;
            }
              
              
             // Process each element
             $output.find('p').each(function() {
            elements.forEach(function(el) {
                var p = this;
                 var $el = $(el);
                if (!p.parentNode) return;
                 var type = classifyElement($el);
                var first = p.firstChild;
                if (!first || first.nodeType !== 1) return;
                if (!first.classList.contains('place-lh') && !first.classList.contains('place-oc')) return;
               
                var $p = $(p);
                 var $prevList = $p.prev('ul, ol, dl');
                if (!$prevList.length) return;
               
                // Pattern A: empty item (Place at start of list item)
                 var $target = findEmptyItem($prevList);
                var isPatternA = $target !== null;
               
                // Pattern B: no empty item (Place at end of list item)
                if (!$target) {
                    $target = $prevList.find('li').last();
                    if (!$target.length) $target = $prevList.find('dd').last();
                    if (!$target.length) return;
                }
               
                if (isPatternA) $target.removeClass('mw-empty-elt');
               
                // Move <p> contents into target item
                while (p.firstChild) {
                    $target[0].appendChild(p.firstChild);
                }
                  
                  
                 switch (type) {
                 // Merge continuation list if same type follows
                    case 'wrapper-2c':
                var $next = $p.next();
                    case 'place-2c':
                if ($next.length && $next[0].tagName === $prevList[0].tagName) {
                        // 2C: flush columns, append at grid root
                    var targetAtRoot = $target[0].parentNode === $prevList[0];
                        flushColumns();
                        layout.append($el);
                        break;
                      
                      
                     case 'place-oc':
                     var children = $next.children().get();
                     case 'place-lh':
                     for (var i = 0; i < children.length; i++) {
                    case 'table-oc':
                        if (isNestingWrapper(children[i])) {
                        // OC types: create marker in IC, collect element
                            var subList = children[i].firstElementChild;
                        ensureColumns();
                            if (targetAtRoot) {
                        var marker = createMarker($el, type);
                                // Pattern B: sub-list becomes child of target
                         currentInner.append(marker);
                                $target[0].appendChild(subList);
                        pendingOC.push($el);
                            } else {
                        break;
                                // Pattern A: sub-list items become siblings
                   
                                var parentList = $target[0].parentNode;
                    case 'book-nav':
                                while (subList.firstChild) {
                        // Collect for later insertion before layout
                                    parentList.appendChild(subList.firstChild);
                        bookNavElements.push($el);
                                }
                        break;
                            }
                      
                        } else {
                     case 'heading':
                            $prevList[0].appendChild(children[i]);
                         // Headings create section boundaries
                         }
                    }
                    $next.remove();
                }
               
                $p.remove();
                repaired++;
            });
           
        }
 
        /* --- Structure Building --- */
       
        function buildStructure() {
            repairListPlacement();
            var parserOutput = $('.mw-parser-output');
            var elements = parserOutput.children().toArray();
           
            layout = $('<div class="preprint-layout"></div>');
            var currentInner = null;
            var currentOuter = null;
            var pendingOC = [];
            var bookNavElements = [];
           
            // Process each element
            elements.forEach(function(el) {
                var $el = $(el);
                var type = classifyElement($el);
               
                switch (type) {
                     case 'wrapper-2c':
                     case 'place-2c':
                         // 2C: flush columns, append at grid root
                         flushColumns();
                         flushColumns();
                         ensureColumns();
                        layout.append($el);
                         currentInner.append($el);
                        break;
                   
                    case 'place-oc':
                    case 'place-lh':
                    case 'table-oc':
                        // OC types: create marker in IC, collect element
                        ensureColumns();
                        var marker = createMarker($el, type);
                        currentInner.append(marker);
                        pendingOC.push($el);
                        break;
                   
                    case 'book-nav':
                        // Collect for later insertion before layout
                        bookNavElements.push($el);
                        break;
                   
                    case 'heading':
                        // Headings create section boundaries
                        flushColumns();
                         ensureColumns();
                         currentInner.append($el);
                         break;
                         break;
                      
                      
Zeile 623: Zeile 748:
                 }
                 }
             });
             });
           
                       
            console.log('Preprint Model: Structure built, ' + linkage.length + ' linkages');
           
             // Helper functions (closure)
             // Helper functions (closure)
             function ensureColumns() {
             function ensureColumns() {
Zeile 666: Zeile 789:
         function applyPortraitProjection() {
         function applyPortraitProjection() {
             if (layout && layout[0]) layout[0].style.paddingBottom = '';
             if (layout && layout[0]) layout[0].style.paddingBottom = '';
             // Clear inline positioning
            $('.preprint-oc-spacer').remove();
             // Clear inline positioning set by landscape projection
             linkage.forEach(function(link) {
             linkage.forEach(function(link) {
                link.element[0].style.top = '';
                 link.element[0].style.position = '';
                 link.element[0].style.position = '';
                link.element[0].style.top = '';
             });
             });
              
              
Zeile 722: Zeile 846:
              
              
             scrollToHash();
             scrollToHash();
            console.log('Preprint Model: Portrait projection applied');
         }
         }
          
          
Zeile 746: Zeile 869:
             }
             }
              
              
             positionOCElements();
             reconcileLayout();
              
              
             // Restore scroll
             // Restore scroll
Zeile 753: Zeile 876:
              
              
             scrollToHash();
             scrollToHash();
            console.log('Preprint Model: Landscape projection applied');
         }
         }
          
          
         function positionOCElements() {
         function reconcileLayout() {
             // Force reflow
            if (!document.body.classList.contains('preprint-landscape')) return;
             if (layout && layout[0]) {
            if (reconcileLayout._running) return;
                void layout[0].offsetHeight;
            reconcileLayout._running = true;
            }
           
            // Disconnect observer to prevent re-entry from our writes
            if (layout._ocObserver) layout._ocObserver.disconnect();
            // Save and reset scroll for accurate offset calculations (Minerva scrolls body)
            var savedBodyScroll = document.body.scrollTop;
            var savedHtmlScroll = document.documentElement.scrollTop;
            document.body.scrollTop = 0;
            document.documentElement.scrollTop = 0;
 
             // === PHASE 1: CLEAN + READ ===
           
            // Remove all previous-cycle artifacts
            $('.preprint-oc-spacer').remove();
             if (layout && layout[0]) layout[0].style.paddingBottom = '';
              
              
            // Position each element relative to its marker
             linkage.forEach(function(link) {
             linkage.forEach(function(link) {
                 var $marker = link.marker;
                 if (link.element[0].style.position !== 'absolute') {
                var $element = link.element;
                    link.element[0].style.position = 'absolute';
                 var $oc = $element.closest('.outer-column');
                    link.element[0].style.top = '0px';
                }
            });
           
            // Force reflow after cleanup
            void document.body.offsetHeight;
                       
            // Read marker positions, OC container offsets, element geometry
            var items = [];
            linkage.forEach(function(link, i) {
                 var $oc = link.element.closest('.outer-column');
                if (!$oc.length) { items.push(null); return; }
                  
                  
                 if (!$oc.length) return;
                 var markerTop = link.marker.offset().top;
                var ocTop = $oc.offset().top;
                  
                  
                 var markerOffset = $marker.offset();
                 items.push({
                var ocOffset = $oc.offset();
                    index: i,
               
                    link: link,
                if (!markerOffset || !ocOffset) return;
                    markerTop: markerTop,
               
                    ocTop: ocTop,
                var relativeTop = markerOffset.top - ocOffset.top;
                    ocEl: $oc[0],
               
                    height: link.element.outerHeight(true),
                $element.css({
                    margin: parseFloat(link.element.css('margin-top')) || 0,
                     'position': 'absolute',
                     landscapeDir: link.element.attr('data-landscape') || null,
                     'top': relativeTop + 'px'
                     top: markerTop - ocTop,
                    docTop: markerTop
                 });
                 });
             });
             });
              
              
             // Landscape placement overrides (elevator)
             // Read first visible IC child offset per OC container (for elevator)
             var landscapeOverrides = linkage.filter(function(link) {
             var ocContainers = [];
                 return link.element.attr('data-landscape');
            var ocFirstICTop = [];
            $('.outer-column').each(function() {
                 ocContainers.push(this);
                var $ic = $(this).prev('.inner-column');
                if (!$ic.length) { ocFirstICTop.push(0); return; }
                var ocTop = $(this).offset().top;
                var found = false;
                $ic.children().each(function() {
                    if (this.getBoundingClientRect().height > 0) {
                        ocFirstICTop.push($(this).offset().top - ocTop);
                        found = true;
                        return false;
                    }
                });
                if (!found) ocFirstICTop.push(0);
             });
             });
             if (landscapeOverrides.length > 0) {
              
                landscapeOverrides.forEach(function(link) {
            // Read protruding IC elements
                    var dir = link.element.attr('data-landscape');
            var protrusions = [];
                    var $oc = link.element.closest('.outer-column');
            $('.inner-column').each(function() {
                    if (!$oc.length) return;
                var icWidth = this.getBoundingClientRect().width;
                    var myTop = parseFloat(link.element.css('top')) || 0;
                $(this).children().each(function() {
                   
                    if (this.classList.contains('oc-marker')) return;
                    // Collect siblings in same column (excluding self), document-relative
                    var elWidth = this.scrollWidth || this.getBoundingClientRect().width;
                    var ocOffset = $oc.offset().top;
                    if (elWidth > icWidth + 1) {
                    var siblings = [];
                         var top = $(this).offset().top;
                    $oc.children('.place-oc, .place-lh, .preprint-table-oc-wrapper').each(function() {
                         protrusions.push({
                        var $el = $(this);
                             element: this,
                        if ($el[0] === link.element[0]) return;
                             top: top,
                        var rect = this.getBoundingClientRect();
                             bottom: top + this.getBoundingClientRect().height
                         var scrollY = window.pageYOffset || document.documentElement.scrollTop;
                         siblings.push({
                             el: $el,
                             top: rect.top + scrollY - ocOffset,
                             bottom: rect.bottom + scrollY - ocOffset
                         });
                         });
                     });
                     }
                     siblings.sort(function(a, b) { return a.top - b.top; });
                });
                   
            });
                    if (dir === 'top') {
           
                        // Find nearest sibling above
            // === PHASE 2: COMPUTE (no DOM access) ===
                        var above = null;
           
                        for (var i = siblings.length - 1; i >= 0; i--) {
            // Elevator adjustments
                            if (siblings[i].top < myTop) { above = siblings[i]; break; }
            items.forEach(function(item) {
                if (!item || !item.landscapeDir) return;
               
                var siblings = items.filter(function(other) {
                     return other && other.ocEl === item.ocEl && other.index !== item.index;
                }).sort(function(a, b) { return a.top - b.top; });
               
                if (item.landscapeDir === 'top') {
                    var above = null;
                    for (var i = siblings.length - 1; i >= 0; i--) {
                        if (siblings[i].top < item.top) { above = siblings[i]; break; }
                    }
                    if (above) {
                        item.top = above.top + above.height - item.margin;
                    } else {
                        var ocIdx = ocContainers.indexOf(item.ocEl);
                        if (ocIdx >= 0) {
                            item.top = ocFirstICTop[ocIdx] - item.margin;
                         }
                         }
                        var margin = parseFloat(link.element.css('margin-top')) || 0;
                        if (above) {
                            link.element.css('top', (above.bottom - margin) + 'px');
                        } else {
                            // No sibling above: align with first visible IC content
                            var $ic = $oc.prev('.inner-column');
                            var targetTop = 0;
                            if ($ic.length) {
                                $ic.children().each(function() {
                                    if (this.getBoundingClientRect().height > 0) {
                                        targetTop = this.getBoundingClientRect().top - $oc[0].getBoundingClientRect().top - margin;
                                        return false;
                                    }
                                });
                            }
                            link.element.css('top', targetTop + 'px');
                        }
                    } else if (dir === 'bottom') {
                        // Find nearest sibling below
                        var below = null;
                        for (var i = 0; i < siblings.length; i++) {
                            if (siblings[i].top > myTop) { below = siblings[i]; break; }
                        }
                        if (below) {
                            link.element.css('top', (below.top - link.element.outerHeight(true)) + 'px');
                        }
                        // If none below, stays at marker position (already at bottom)
                     }
                     }
                 });
                 } else if (item.landscapeDir === 'bottom') {
                    var below = null;
                    for (var i = 0; i < siblings.length; i++) {
                        if (siblings[i].top > item.top) { below = siblings[i]; break; }
                    }
                    if (below) {
                        item.top = below.top - item.height;
                    }
                }
                item.docTop = item.ocTop + item.top;
            });
           
            // Fix overlaps (type-pair minimum gaps)
            var valid = items.filter(function(item) { return item !== null; });
            valid.sort(function(a, b) { return a.docTop - b.docTop; });
           
            function elementGap(item) {
                if (item.link.type === 'place-lh') return 2;
                if (item.height >= 50) return 16;
                return 4;
             }
             }
              
              
             // Fix overlaps
             var lastItem = null;
            fixOverlaps();
             valid.forEach(function(item) {
             // Correct OC position to align with adjacent IC content
                 if (lastItem) {
            linkage.forEach(function(link) {
                    var minGap = Math.max(elementGap(item), elementGap(lastItem));
                 var nextIC = link.marker[0].nextElementSibling;
                    var requiredTop = lastItem.docTop + lastItem.height + minGap;
                if (!nextIC) return;
                    var overlap = requiredTop - item.docTop;
                // Only correct for block-level siblings (tables, divs), not inline elements within a paragraph
                    if (overlap > 0) {
                if (nextIC.parentNode && nextIC.parentNode.tagName === 'P') return;
                        item.top += overlap;
                var ocEl = link.element[0];
                        item.docTop += overlap;
                var icInner = nextIC.querySelector('table') || nextIC;
                    }
                var ocInner = ocEl.querySelector('table') || ocEl;
                var delta = icInner.getBoundingClientRect().top - ocInner.getBoundingClientRect().top;
                if (Math.abs(delta) > 1) {
                    var current = parseFloat(link.element.css('top')) || 0;
                    link.element.css('top', (current + delta) + 'px');
                 }
                 }
                lastItem = item;
             });
             });
            adjustLayoutPadding();
              
              
             // Watch for collapsible table expand/collapse
             // Conflict detection: protruding IC vs computed OC positions
             if (!layout._ocObserver) {
             var spacers = [];
                 layout._ocObserver = new MutationObserver(adjustLayoutPadding);
            protrusions.forEach(function(prot) {
                 layout._ocObserver.observe(layout[0], { subtree: true, attributes: true, attributeFilter: ['class'] });
                 var maxConflictBottom = 0;
            }
                valid.forEach(function(item) {
        }
                    var ocBottom = item.docTop + item.height;
       
                    if (prot.top < ocBottom && prot.bottom > item.docTop) {
        function fixOverlaps() {
                        if (ocBottom > maxConflictBottom) maxConflictBottom = ocBottom;
            // Force reflow first
                    }
            void document.body.offsetHeight;
                });
                 if (maxConflictBottom > 0) {
                    var pushdown = maxConflictBottom - prot.top;
                    if (pushdown > 0) {
                        spacers.push({ element: prot.element, height: pushdown });
                    }
                }
            });
              
              
             // Collect elements with document-relative positions
             // === PHASE 3: WRITE (single pass) ===
             var scrollY = window.pageYOffset || document.documentElement.scrollTop;
              
             var allOC = [];
             // Set OC positions
             $('.outer-column').each(function() {
             valid.forEach(function(item) {
                 var $oc = $(this);
                 item.link.element.css({
                $oc.children('.place-oc, .place-lh, .preprint-table-oc-wrapper').each(function() {
                     'position': 'absolute',
                     var $el = $(this);
                    'top': item.top + 'px'
                    var rect = this.getBoundingClientRect();
                    allOC.push({
                        el: $el,
                        oc: $oc,
                        top: rect.top + scrollY,
                        bottom: rect.bottom + scrollY
                    });
                 });
                 });
             });
             });
            // Sort by document-relative top
            allOC.sort(function(a, b) { return a.top - b.top; });
              
              
             // Fix overlaps using document-relative positions
             // Insert spacers
             var lastBottom = 0;
             spacers.forEach(function(s) {
            allOC.forEach(function(item, index) {
                 var spacer = document.createElement('div');
                 var overlap = lastBottom - item.top;
                spacer.className = 'preprint-oc-spacer';
                if (overlap > 0) {
                spacer.style.height = s.height + 'px';
                    // Push down by overlap amount
                 s.element.parentNode.insertBefore(spacer, s.element);
                    var currentTop = parseFloat(item.el.css('top')) || 0;
                    item.el.css('top', (currentTop + overlap) + 'px');
                    item.top += overlap;
                    item.bottom += overlap;
                 }
               
                // Gap 4px
                var nextIsTable = (index + 1 < allOC.length) &&
                    allOC[index + 1].el.hasClass('preprint-table-oc-wrapper');
                var gap = 4;
               
                lastBottom = item.bottom + gap;
             });
             });
        }
           
 
            // Final measure: layout padding
        function adjustLayoutPadding() {
             void document.body.offsetHeight;
             var el = document.querySelector('.preprint-layout');
             var scrollY = window.pageYOffset || 0;
             if (!el) return;
             var layoutBottom = layout[0].getBoundingClientRect().bottom + scrollY;
             var layoutRect = el.getBoundingClientRect();
             var maxOCBottom = 0;
            var currentPad = parseFloat(el.style.paddingBottom || 0);
             valid.forEach(function(item) {
             var maxBottom = 0;
                 var rect = item.link.element[0].getBoundingClientRect();
             $('.outer-column .place-oc, .outer-column .place-lh, .outer-column .preprint-table-oc-wrapper').each(function() {
                 if (rect.bottom + scrollY > maxOCBottom) maxOCBottom = rect.bottom + scrollY;
                 var rect = this.getBoundingClientRect();
                 if (rect.bottom > maxBottom) maxBottom = rect.bottom;
             });
             });
            var overflow = maxBottom - layoutRect.bottom + currentPad;
             if (maxOCBottom > layoutBottom) {
             if (overflow > 0) {
                 layout[0].style.paddingBottom = (maxOCBottom - layoutBottom) + 'px';
                 el.style.paddingBottom = overflow + 'px';
             }
             }
         }
           
 
            // Restore scroll
         // Reposition after images load (initial positioning may precede image render)
            document.body.scrollTop = savedBodyScroll;
         $('.preprint-layout img').on('load', function() {
            document.documentElement.scrollTop = savedHtmlScroll;
            reconcileLayout._running = false;
           
            // Reconnect or create observer
            if (!layout._ocObserver) {
                layout._ocObserver = new MutationObserver(function() {
                    clearTimeout(layout._ocDebounce);
                    layout._ocDebounce = setTimeout(reconcileLayout, 300);
                });
            }
            layout._ocObserver.observe(layout[0], {
                subtree: true, attributes: true, attributeFilter: ['class']
            });
           
            // ResizeObserver: catches container width changes not visible to
            // window resize (TOC pin/unpin, tool menu toggle, DevTools panel).
            // Created once, never disconnected — only fires on actual size change.
            if (!layout._resizeObserver && typeof ResizeObserver !== 'undefined') {
                layout._resizeObserver = new ResizeObserver(function() {
                    clearTimeout(layout._resizeDebounce);
                    layout._resizeDebounce = setTimeout(function() {
                        if (PAL.isOrientationLocked()) return;
                        if (document.body.classList.contains('preprint-landscape')) {
                            reconcileLayout();
                        }
                    }, 200);
                });
                layout._resizeObserver.observe(layout[0]);
            }
         }
 
         // Reposition after images load (initial positioning may precede image render)
         $('.preprint-layout img').on('load', function() {
             if (document.body.classList.contains('preprint-landscape')) {
             if (document.body.classList.contains('preprint-landscape')) {
                 positionOCElements();
                 reconcileLayout();
             }
             }
         });
         });
Zeile 948: Zeile 1.122:
                     if (PAL.isOrientationLocked()) return;
                     if (PAL.isOrientationLocked()) return;
                     if (document.body.classList.contains('preprint-landscape')) {
                     if (document.body.classList.contains('preprint-landscape')) {
                         positionOCElements();
                         reconcileLayout();
                     }
                     }
                 }, 200);
                 }, 200);
Zeile 961: Zeile 1.135:
                     el.scrollIntoView({ block: 'start' });
                     el.scrollIntoView({ block: 'start' });
                     window.scrollBy(0, -60);
                     window.scrollBy(0, -60);
                    console.log('Preprint Model: Scrolled to return target ' + returnTarget);
                 }
                 }
                 returnTarget = null;
                 returnTarget = null;
Zeile 973: Zeile 1.146:
                 target.scrollIntoView({ block: 'start' });
                 target.scrollIntoView({ block: 'start' });
                 window.scrollBy(0, -60);
                 window.scrollBy(0, -60);
                console.log('Preprint Model: Scrolled to hash ' + window.location.hash);
             }
             }
         }
         }
Zeile 1.086: Zeile 1.258:
             getLinkage: getLinkage,
             getLinkage: getLinkage,
             applyProjection: applyProjection,
             applyProjection: applyProjection,
             positionOCElements: positionOCElements,
             positionOCElements: reconcileLayout,
             captureAnchor: captureAnchor,
             captureAnchor: captureAnchor,
             restoreAnchor: restoreAnchor,
             restoreAnchor: restoreAnchor,
             getPositionAnchor: getPositionAnchor,
             getPositionAnchor: getPositionAnchor,
             getLayout: function() { return layout; },
             getLayout: function() { return layout; },
             set returnTarget(id) { returnTarget = id; }
             set returnTarget(id) { returnTarget = id; },
            pauseObserver: function() {
                if (layout && layout._ocObserver) layout._ocObserver.disconnect();
            },
            resumeObserver: function() {
                if (layout && layout[0] && layout._ocObserver) {
                    layout._ocObserver.observe(layout[0], {
                        subtree: true, attributes: true, attributeFilter: ['class']
                    });
                }
            }
         };
         };
     })();
     })();
Zeile 1.156: Zeile 1.338:
                 var $icon = $('<a class="book-nav-icon"></a>');
                 var $icon = $('<a class="book-nav-icon"></a>');
                 if (isBookRoot) {
                 if (isBookRoot) {
                     $icon.attr('href', '/');
                     var saved = sessionStorage.getItem('preprint-position');
                    if (saved) {
                        var pos = JSON.parse(saved);
                        $icon.attr('href', '/wiki/' + pos.article);
                    } else {
                        $icon.attr('href', '#');
                        $icon.addClass('book-nav-icon-disabled');
                    }
                 } else {
                 } else {
                     $icon.attr('href', '/wiki/' + book + (rootAnchor ? '#' + rootAnchor : ''));
                     $icon.attr('href', '/wiki/' + book + (rootAnchor ? '#' + rootAnchor : ''));
Zeile 1.171: Zeile 1.360:
                             var pos = JSON.parse(saved);
                             var pos = JSON.parse(saved);
                             window.location.href = '/wiki/' + pos.article;
                             window.location.href = '/wiki/' + pos.article;
                        } else {
                            window.location.href = '/';
                         }
                         }
                        // No action when disabled — no homepage fallback
                     });
                     });
                 } else {
                 } else {
Zeile 1.202: Zeile 1.390:
                 }
                 }
             }
             }
           
            console.log('Preprint Features: BookNav icon (root=' + isBookRoot + ')');
         }
         }


Zeile 1.269: Zeile 1.455:
              
              
             $landmark.before($link);
             $landmark.before($link);
             console.log('Preprint Features: Sidebar nav (root=' + isBookRoot + ')');
             if (mw.config.get('wgUserName')) {
                var style = document.createElement('style');
                style.textContent = '@media(max-width:790px){.book-nav-sidebar{display:none!important}}';
                document.head.appendChild(style);
            }
         }
         }


Zeile 1.411: Zeile 1.601:
                 // Mobile: root page IS the TOC, hide MW-generated one
                 // Mobile: root page IS the TOC, hide MW-generated one
                 $('.toc, #toc').hide();
                 $('.toc, #toc').hide();
                console.log('Preprint Features: TOC suppressed (bookRoot+Minerva)');
             } else if (isPreprintLayout && bookNavData.length && PAL.isMinerva()) {
             } else if (isPreprintLayout && bookNavData.length && PAL.isMinerva()) {
                 // Mobile book chapters: linear reading, no TOC
                 // Mobile book chapters: linear reading, no TOC
                 $('.toc, #toc').hide();
                 $('.toc, #toc').hide();
                console.log('Preprint Features: TOC suppressed (chapter+BookNav+Minerva)');
             }
             }
             // Desktop: TOC always visible (sticky sidebar navigation)
             // Desktop: TOC always visible (sticky sidebar navigation)
Zeile 1.460: Zeile 1.648:
             return;
             return;
         }
         }
       
               
        console.log('Preprint: PAL ready, building structure');
       
         // Build structure (Model)
         // Build structure (Model)
         Model.buildStructure();
         Model.buildStructure();
Zeile 1.470: Zeile 1.656:
         setTimeout(function() {
         setTimeout(function() {
             Model.applyProjection(initialMode);
             Model.applyProjection(initialMode);
            Model.pauseObserver();
             $('.preprint-layout').addClass('preprint-ready');
             $('.preprint-layout').addClass('preprint-ready');
            Model.resumeObserver();
            window.preprintReadyAt = performance.now();
         }, 50);
         }, 50);


Zeile 1.508: Zeile 1.697:
             var icon = document.querySelector('.book-nav-icon');
             var icon = document.querySelector('.book-nav-icon');
             if (icon) icon.style.visibility = 'hidden';
             if (icon) icon.style.visibility = 'hidden';
            var el = document.querySelector('.preprint-layout');
            if (el) { el.style.transition = 'none'; el.style.opacity = '0'; }
         });
         });
          
          
         PAL.onProjectionChange(function(event) {
         PAL.onProjectionChange(function(event) {
            console.log('Preprint: Projection change ' + event.from + ' → ' + event.to);
              
              
             // Icon already hidden by onPreProjectionChange
             // Icon already hidden by onPreProjectionChange
Zeile 1.524: Zeile 1.714:
                     Features.calculateIconMetrics();
                     Features.calculateIconMetrics();
                     if (icon) icon.style.visibility = '';
                     if (icon) icon.style.visibility = '';
                    Model.positionOCElements();
                 }, 100);
                 }, 100);
             } else {
             } else {
Zeile 1.535: Zeile 1.726:
             Model.restoreAnchor();
             Model.restoreAnchor();
              
              
         });
            // Reveal layout after projection complete
            var el = document.querySelector('.preprint-layout');
            if (el) {
                setTimeout(function() {
                    el.style.transition = 'opacity 0.15s';
                    el.style.opacity = '1';
                }, 30);
            }
         });
          
          
         // Expose for debugging and external access
         // Expose for debugging and external access
Zeile 1.549: Zeile 1.748:
         });
         });
          
          
         mw.loader.load('/wiki/MediaWiki:Common.js/debug.js?action=raw&ctype=text/javascript');         
         // mw.loader.load('/wiki/MediaWiki:Common.js/debug.js?action=raw&ctype=text/javascript');         
        console.log('Preprint: Initialization complete (v7.17)');
     });
     });


})();
})();


/* === PREPRINT END === */


/* === PREPRINT END === */




/* Unified Consent System JavaScript v250911r1
/* === CONSENT START === */
/* Unified Consent System JavaScript v260224r1
  * Single file for both EN and DE versions
  * Single file for both EN and DE versions
  * Automatically detects language based on domain
  * Automatically detects language based on domain
Zeile 1.598: Zeile 1.797:
}
}


// Handle consent acquisition box - 90-day reminder for everyone
// Handle consent acquisition box - 90-day reminder for visitors (spec §8.9)
$(document).ready(function() {
$(document).ready(function() {
     var consentBox = document.getElementById('consent-acquisition-box');
     var consentBox = document.getElementById('consent-acquisition-box');
Zeile 1.620: Zeile 1.819:
         // ADD THIS LINE:
         // ADD THIS LINE:
         localStorage.setItem('consent-acquisition-dismissed-until', Date.now() + (90 * 24 * 60 * 60 * 1000));
         localStorage.setItem('consent-acquisition-dismissed-until', Date.now() + (90 * 24 * 60 * 60 * 1000));
         var targetPage = getCurrentLanguage() === 'de' ?  
         if (mw.config.get('wgUserName')) {
            '/wiki/Transformal_GmbH:Einstellungen' :  
            window.location.href = '/wiki/Special:Preferences#mw-prefsection-legal';
            '/wiki/Transformal_GmbH:Settings';
        } else {
        window.location.href = targetPage;
            var targetPage = getCurrentLanguage() === 'de' ?  
                '/wiki/Transformal_GmbH:Einstellungen' :  
                '/wiki/Transformal_GmbH:Settings';
            window.location.href = targetPage;
        }
     });
     });
});
});
Zeile 1.721: Zeile 1.924:
     }
     }
}
}
/* === CONSENT END === */
/* === CONDITIONAL START === */


/* Template 'ConditionalContent' - v250925r8 */
/* Template 'ConditionalContent' - v250925r8 */
Zeile 1.753: Zeile 1.960:
             } else {
             } else {
                 $(this).css('display', 'inline');
                 $(this).css('display', 'inline');
             }
             }
         });
         });
         $('.logged-out-only').css('display', 'none');
         $('.logged-out-only').css('display', 'none');
          
          
         // Block content
         // Block content
         $('.logged-in-section').each(function() {
         $('.logged-in-section').each(function() {
             if ($(this).is(':empty') || !$(this).text().replace(/\s/g, '')) {
             if ($(this).is(':empty') || !$(this).text().replace(/\s/g, '')) {
                 $(this).css('display', 'none');
                 $(this).css('display', 'none');
             } else {
             } else {
                 $(this).css('display', 'block');
                 $(this).css('display', 'block');
             }
             }
         });
         });
         $('.logged-out-section').css('display', 'none');
         $('.logged-out-section').css('display', 'none');
     }
     }
 
    // Clean TOC
    var toc = $('.vector-toc')[0] || $('#toc')[0] || $('.toc')[0];
    if (toc) {
        $('.logged-in-section:hidden, .logged-out-section:hidden').each(function() {
            $(this).find('h1, h2, h3, h4, h5, h6').each(function() {
                var id = this.id || $(this).find('.mw-headline').attr('id');
                if (id) {
                    $(toc).find('a[href="#' + id + '"]').closest('li').remove();
                }
            });
        });
    }
});
 


    // Clean TOC
/* === CONDITIONAL END === */
    var toc = $('.vector-toc')[0] || $('#toc')[0] || $('.toc')[0];
    if (toc) {
        $('.logged-in-section:hidden, .logged-out-section:hidden').each(function() {
            $(this).find('h1, h2, h3, h4, h5, h6').each(function() {
                var id = this.id || $(this).find('.mw-headline').attr('id');
                if (id) {
                    $(toc).find('a[href="#' + id + '"]').closest('li').remove();
                }
            });
        });
    }
});