Zum Inhalt springen


MediaWiki:Common.js: Unterschied zwischen den Versionen

Aus Firestone Idle RPG Wiki
Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
 
(23 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 1: Zeile 1:
/* Das folgende JavaScript wird für alle Benutzer geladen. */
/* Das folgende JavaScript wird für alle Benutzer geladen. */
/*
/* ==== Sidebar: klickbare Kopfzeile + Gruppen mit Auf/Zu + Persistenz (Vector-2022 & Drawer) ==== */
/* Sidebar: Heading als Link + Untermenüs (#group:) – Vector 2022 */
(function () {
mw.loader.using( ['mediawiki.util'] ).then(function () {
  'use strict';
   if ( mw.config.get('skin') !== 'vector-2022' ) return;
   if ( mw.config.get('skin') !== 'vector-2022' ) return;


   var panel = document.getElementById('mw-panel');
  /* ---------- Persistenz ---------- */
   if (!panel) return;
   var STORAGE_KEY = 'kr-sb-state:v3:' + ( mw.config.get('wgUserName') || 'anon' );
   function loadState() {
    try { return JSON.parse( localStorage.getItem(STORAGE_KEY) || '{}' ); }
    catch (e) { return {}; }
  }
  function saveState(state) {
    try { localStorage.setItem( STORAGE_KEY, JSON.stringify(state) ); }
    catch (e) {}
  }
  var STATE = loadState();


   function getDirective(el, key) {
  /* ---------- Utils ---------- */
     // prüft href (inkl. URL-encodiert), data-href, title, Text
   function extractDirective(a) {
     var href = (el.getAttribute('href') || '');
     if (!a) return null;
     if (href.indexOf('#' + key + ':') === 0) return href;                // "#link:..."
     var href = a.getAttribute('href') || '';
    if (href.indexOf('%23' + key + ':') !== -1) return href;              // "...%23link:..."
     try { href = new URL(href, location.href).hash || ''; } catch (e) {}
     var dh  = el.getAttribute('data-href') || '';
     var s = (href || '').trim().toLowerCase();
     if (dh.indexOf('#' + key + ':') === 0) return dh;
     if (s.includes('#group:')) return { kind: 'group', value: decodeURIComponent(s.split('#group:').pop()) };
    var tt  = el.getAttribute('title') || '';
     if (s.includes('#link:')) return { kind: 'link',  value: decodeURIComponent(s.split('#link:').pop()) };
     if (tt.indexOf('#' + key + ':') === 0) return tt;
    var tx  = (el.textContent || '').trim();
    if (tx.indexOf('#' + key + ':') === 0) return tx;
     return null;
     return null;
   }
   }
   function directiveTarget(directiveValue) {
   function stripDirectiveHash(a) {
     // extrahiert den Teil nach "link:" bzw. "group:"
     if (!a) return;
     var v = decodeURIComponent(directiveValue.replace(/^.*?(#|%23)(link|group):/, ''));
     a.setAttribute('href', (a.getAttribute('href') || '').replace(/#(?:link|group):[^#]*$/i, '') );
     return v.replace(/^\s+|\s+$/g, '');
  }
  function hasRealHref(a) {
    if (!a) return false;
     return !/^#/.test( a.getAttribute('href') || '' );
  }
  function normId(s) {
    return String(s || '').trim().toLowerCase()
      .replace(/\s+/g, '-').replace(/[^\w\-:.|]/g, '');
  }
  function toggleSet(li, arrow, expanded) {
    if (expanded) { li.classList.remove('is-collapsed'); arrow.setAttribute('aria-expanded','true'); }
    else          { li.classList.add('is-collapsed');    arrow.setAttribute('aria-expanded','false'); }
  }
 
  /* ---------- Kopf + ID ---------- */
  function makeHead(li, keepLink, containerUL, labelOverride) {
    var a = li.querySelector(':scope > a');
 
    var head = document.createElement('div');
    head.className = 'kr-head has-toggle'; // ganze Kopfzeile darf toggeln (außer Link)
 
    var btn = document.createElement('button');
    btn.type = 'button';
    btn.className = 'kr-arrow';
    btn.setAttribute('aria-expanded', 'false');
    head.appendChild(btn);
 
    if (keepLink && a) { stripDirectiveHash(a); head.appendChild(a); }
    else {
      var span = document.createElement('span');
      span.className = 'kr-title';
      span.textContent = labelOverride || (a && a.textContent) || '';
      head.appendChild(span);
      if (a) a.remove();
    }
 
    li.insertBefore(head, li.firstChild);
 
    // stabile ID für Persistenz
    var portlet = containerUL.closest('.vector-menu');
    var pid = (portlet && (portlet.id || portlet.getAttribute('id'))) || 'portlet';
    var labelText = (labelOverride || (head.querySelector('a, .kr-title') || {}).textContent || '').trim();
    li.dataset.krId = normId( pid + '::' + labelText );
 
    // Default zu; gespeicherten Zustand anwenden
    toggleSet(li, btn, false);
    var st = STATE[ li.dataset.krId ];
    if (st === 1) toggleSet(li, btn, true);
 
    return head;
  }
 
  /* ---------- Toggle-Handler ---------- */
  function handleArrowClick(btn) {
    var li = btn.closest('li.kr-group, li.kr-top');
    if (!li) return;
 
    var willExpand = li.classList.contains('is-collapsed');
    toggleSet(li, btn, willExpand);
 
    var id = li.dataset.krId;
    if (id) {
      STATE[id] = willExpand ? 1 : 0;
      saveState(STATE);
    }
   }
   }
  function wikiHref(title) { return mw.util.getUrl(title); }


   function makeToggleBtn(expanded) {
   /* Globale Delegation: Pfeil klickt immer; Kopfzeile klickt nur, wenn NICHT im Link */
     var b = document.createElement('button');
  document.addEventListener('click', function (e) {
    b.type = 'button';
     var arrow = e.target && e.target.closest && e.target.closest('.kr-arrow');
    b.className = 'kr-sb-toggle';
     if (arrow) {
    b.setAttribute('aria-expanded', expanded ? 'true' : 'false');
     b.setAttribute('title', expanded ? 'Zuklappen' : 'Aufklappen');
    b.addEventListener('click', function (e) {
       e.preventDefault();
       e.preventDefault();
       var wrap = b.closest('.kr-sb-wrap, .kr-sb-group');
       handleArrowClick(arrow);
       var isCollapsed = wrap.classList.toggle('is-collapsed');
      return;
      b.setAttribute('aria-expanded', isCollapsed ? 'false' : 'true');
    }
    var head = e.target && e.target.closest && e.target.closest('.kr-head');
    if (head && !e.target.closest('.kr-head a')) {
      e.preventDefault(); // freie Fläche der Kopfzeile
      var btn = head.querySelector('.kr-arrow');
      if (btn) handleArrowClick(btn);
    }
  }, true); // capture=true, damit nichts dazwischenfunkt
 
  /* ---------- Aufbau der Gruppen ---------- */
  function buildGroups(ul) {
    var lis = Array.from( ul.querySelectorAll(':scope > li.mw-list-item') );
    var current = null;
 
    lis.forEach(function (li) {
      var a = li.querySelector(':scope > a');
       var d = extractDirective(a);
 
      if (d && d.kind === 'group') {
        var clickable = hasRealHref(a);
        li.classList.add('kr-group');
        makeHead(li, clickable, ul, (a && a.textContent) || d.value || '');
        if (!clickable && a) a.remove(); else if (clickable) stripDirectiveHash(a);
 
        var sub = document.createElement('ul');
        sub.className = 'kr-sub';
        li.appendChild(sub);
        current = sub;
        return;
      }
 
      if (current && !(d && (d.kind === 'group' || d.kind === 'link'))) {
        current.appendChild(li);
      }
     });
     });
    return b;
   }
   }


   panel.querySelectorAll('.vector-menu-portal').forEach(function (portlet) {
   /* ---------- Portlets verarbeiten ---------- */
     var heading = portlet.querySelector('.vector-menu-heading');
  function buildPortlets(root) {
    var list    = portlet.querySelector('.vector-menu-content-list');
     var lists = root.querySelectorAll('.vector-menu .vector-menu-content-list');
     if (!heading || !list) return;
     lists.forEach(function (ul) {
      if (ul.dataset.krSbDone) return;
      ul.dataset.krSbDone = '1';


    // 1) #link:… → Überschrift klickbar machen
      var items  = Array.from( ul.querySelectorAll(':scope > li.mw-list-item') );
    var linkDirectiveAnchor = Array.from(list.querySelectorAll('li > a')).find(function (a) {
      var topIdx = items.findIndex(function (li) {
       return !!getDirective(a, 'link');
        var a = li.querySelector(':scope > a');
        var d = extractDirective(a);
        return d && d.kind === 'link';
      });
 
      if (topIdx >= 0) {
        var top = items[topIdx];
        top.classList.add('kr-top');
        makeHead(top, true, ul);
        stripDirectiveHash( top.querySelector(':scope > a') );
 
        var sub = document.createElement('ul');
        sub.className = 'kr-sub';
        top.appendChild(sub);
 
        for (var i = topIdx + 1; i < items.length; i++) sub.appendChild(items[i]);
        buildGroups(sub);
      } else {
        buildGroups(ul);
      }
 
       // Direktiven aus hrefs entfernen; Vector-Portlet-Heading ausblenden
      ul.querySelectorAll('a[href*="#link:"], a[href*="#group:"]').forEach(stripDirectiveHash);
      var portlet = ul.closest('.vector-menu');
      var heading = portlet && portlet.querySelector(':scope > .vector-menu-heading');
      if (heading) heading.remove();
     });
     });
     if (linkDirectiveAnchor) {
  }
       var dir = getDirective(linkDirectiveAnchor, 'link');
 
       var target = directiveTarget(dir) || 'Hauptseite';
  /* ---------- Bootstrapping ---------- */
      var labelEl = heading.querySelector('.vector-menu-heading-label') || heading;
  function processRoot(root) {
      var labelText = labelEl.textContent.trim() || target;
     if (!root || root.dataset.krSbRootDone) return;
       var link = document.createElement('a');
    root.dataset.krSbRootDone = '1';
       link.className = 'kr-sb-heading-link';
    root.classList.add('kr-sb');
      link.href = wikiHref(target);
    buildPortlets(root);
       link.textContent = labelText;
  }
       labelEl.textContent = '';
 
       labelEl.appendChild(link);
  function init() {
      // Direktive entfernen
    document.querySelectorAll('#mw-panel, #vector-main-menu, #vector-main-menu-pinned, .vector-drawer')
      linkDirectiveAnchor.closest('li').remove();
       .forEach(processRoot);
  }
 
  init();
  mw.hook('wikipage.content').add(init);
 
  // Drawer/DOM-Änderungen beobachten
  var mo = new MutationObserver(function (muts) {
    muts.forEach(function (m) {
      m.addedNodes && m.addedNodes.forEach(function (n) {
        if (!(n instanceof Element)) return;
        if (n.matches('#vector-main-menu, #vector-main-menu-pinned, .vector-drawer, #mw-panel')) processRoot(n);
        n.querySelectorAll && n.querySelectorAll('#vector-main-menu, #vector-main-menu-pinned, .vector-drawer, #mw-panel')
          .forEach(processRoot);
       });
    });
  });
  mo.observe(document.body, { childList: true, subtree: true });
})();
 
/* TabberNeue: Anker-Scroll unter Vector-Header + robustes „Pinning“ */
(function () {
  /* reale Sticky-Höhe (+0.5rem Luft) – korrekt für Gäste */
  function stickyOffsetPx() {
    var air = Math.round(parseFloat(getComputedStyle(document.documentElement).fontSize) * 0.5);
 
    // eingeloggt zuverlässig erkennen (Logout-Link existiert nur dann)
    var isLoggedIn = !!document.getElementById('pt-logout');
 
    // Vector 2022 Header-Container
    var hdr = document.querySelector('.vector-sticky-header-container, #vector-sticky-header');
    if (!isLoggedIn || !hdr) return air;            // Gäste: nur Luft
 
    // nur zählen, wenn der Header wirklich sichtbar/aktiv ist
    var cs = getComputedStyle(hdr);
    var op = parseFloat(cs.opacity || '1');
    var r  = hdr.getBoundingClientRect();
    var visible = op > 0.01 && r.top <= 0 && r.bottom > 0;
 
    var h = visible ? Math.round(r.height) : 0;
    if (!h && isLoggedIn) h = 50;                  // letzte Rettung nur für eingeloggte
    return h + air;
  }
 
  /* oberste Tabber-Leiste finden */
  function headerForPanel(panel) {
    if (!panel) return null;
    var top = panel.closest('.tabber');
    for (var cur = top; cur && cur.parentElement;) {
       var up = cur.parentElement.closest('.tabber');
       if (!up) break;
      top = up; cur = up;
    }
    return top ? top.querySelector(':scope > .tabber__header') : null;
  }
 
  /* Panel/Tabber, die wir beobachten wollen */
  function getObserved(tabber) {
    var section = tabber && tabber.querySelector(':scope > .tabber__section');
    return { tabber: tabber, section: section };
  }
 
  /* „Pinne“ den Viewport hart auf header/panel; löse erst, wenn stabil */
  function pinScrollToTarget(panelOrHeader) {
    var target = panelOrHeader;
    if (!target) return;
 
    var tabber = target.closest('.tabber');
    var obs = getObserved(tabber);
 
    /* Freeze: Höhe zum Start lesen und auf dem Container festhalten */
    if (tabber && obs.section) {
       var h0 = Math.ceil(obs.section.getBoundingClientRect().height);
       tabber.style.setProperty('--tabber-freeze-h', h0 + 'px');
       tabber.classList.add('tabber-is-switching');
    }
 
    /* Smooth-Scrolling global aus, damit nichts „nachfedert“ */
    document.documentElement.classList.add('fs-scroll-now');
 
    /* Fremde scrollIntoView()-Aufrufe währenddessen blocken */
    var origScrollIntoView = Element.prototype.scrollIntoView;
    Element.prototype.scrollIntoView = function(){ /* noop while pinned */ };
 
    /* Beobachte Größenänderungen → Stability Counter zurücksetzen */
    var stable = 0, lastH = -1, raf = 0;
    var ro = null;
    if (obs.section && window.ResizeObserver) {
      ro = new ResizeObserver(function(){ stable = 0; });
      try { ro.observe(obs.section); } catch(e){}
    }
 
    var MAX_MS = 3000;
    var t0 = performance.now();
 
    function wantY() {
      return target.getBoundingClientRect().top + window.scrollY - stickyOffsetPx();
     }
     }


     // 2) Portlet-Toggle hinzufügen (Heading bleibt Link, Pfeil klappt)
     function release() {
    if (!portlet.classList.contains('kr-sb-wrap')) {
      if (ro) ro.disconnect();
      portlet.classList.add('kr-sb-wrap');
      Element.prototype.scrollIntoView = origScrollIntoView;
      heading.appendChild( makeToggleBtn(true) );
      document.documentElement.classList.remove('fs-scroll-now');
      if (tabber) {
        tabber.classList.remove('tabber-is-switching');
        tabber.style.removeProperty('--tabber-freeze-h');
      }
      cancelAnimationFrame(raf);
     }
     }


     // 3) #group:… → Untermenüs bauen
     function tick() {
    var items = Array.from(list.children); // <li>…
       var y = wantY();
    for (var i = 0; i < items.length; i++) {
       if (Math.abs(window.scrollY - y) > 1) {
       var li = items[i];
        /* Hart auf die Ziel-Position setzen */
       if (!li) continue;
        window.scrollTo(0, y);
      var a  = li.querySelector('a');
        stable = 0; /* wir mussten korrigieren → noch nicht stabil */
      if (!a) continue;
       }
      var gdir = getDirective(a, 'group');
       if (!gdir) continue;


       var groupLabel = a.textContent.trim() || directiveTarget(gdir) || 'Gruppe';
      /* aktuelles Maß nehmen (Section bevorzugt, sonst Tabber) */
       li.textContent = '';
       var hNow = 0;
      li.classList.add('kr-sb-group');
      if (obs.section) hNow = obs.section.getBoundingClientRect().height;
       else if (tabber)  hNow = tabber.getBoundingClientRect().height;


       var header = document.createElement('div');
       if (Math.abs(hNow - lastH) < 1 && Math.abs(window.scrollY - y) <= 1) {
      header.className = 'kr-sb-group-header';
        stable++;
      var titleSpan = document.createElement('span');
       } else {
       titleSpan.className = 'kr-sb-group-title';
        stable = 0;
      titleSpan.textContent = groupLabel;
        lastH = hNow;
      var btn = makeToggleBtn(true);
       }
      header.appendChild(titleSpan);
       header.appendChild(btn);
      li.appendChild(header);


       var sub = document.createElement('ul');
       /* 12 stabile Frames ≈ ~200ms bei 60fps */
       sub.className = 'kr-sb-sub';
       if (stable >= 12 || (performance.now() - t0) > MAX_MS) {
      while (items[i+1] && !getDirective((items[i+1].querySelector('a')||{}), 'group')) {
         release();
         sub.appendChild(items[i+1]);
         return;
         items[i+1] = null; // verbraucht
        i++;
       }
       }
       li.appendChild(sub);
       raf = requestAnimationFrame(tick);
     }
     }
    /* sofort starten */
    raf = requestAnimationFrame(tick);
  }
  function scrollToTab(hashOrId) {
    var name = (hashOrId || '').replace(/^#/, '');
    if (!/^tabber-/.test(name)) return;
    var panel  = document.getElementById(name);
    if (!panel) return;
    var header = headerForPanel(panel) || panel;
    pinScrollToTarget(header);
  }
  /* 1) Direkt mit Hash geladen */
  if (/^#tabber-/.test(location.hash)) {
    setTimeout(function(){ scrollToTab(location.hash); }, 60);
  }
  /* 2) Hashwechsel */
  window.addEventListener('hashchange', function () {
    if (/^#tabber-/.test(location.hash)) scrollToTab(location.hash);
   });
   });
});
 
  /* 3) Klicks (TabberNeue nutzt History API, verhindert default) */
  document.addEventListener('click', function (e) {
    var a = e.target && e.target.closest('a[href^="#tabber-"], button[aria-controls^="tabber-"]');
    if (!a) return;
    var id = a.getAttribute('href') || ('#' + a.getAttribute('aria-controls'));
    setTimeout(function(){ scrollToTab(id); }, 60);
  }, true);
})();

Aktuelle Version vom 30. Oktober 2025, 12:08 Uhr

/* Das folgende JavaScript wird für alle Benutzer geladen. */
/* ==== Sidebar: klickbare Kopfzeile + Gruppen mit Auf/Zu + Persistenz (Vector-2022 & Drawer) ==== */
(function () {
  'use strict';
  if ( mw.config.get('skin') !== 'vector-2022' ) return;

  /* ---------- Persistenz ---------- */
  var STORAGE_KEY = 'kr-sb-state:v3:' + ( mw.config.get('wgUserName') || 'anon' );
  function loadState() {
    try { return JSON.parse( localStorage.getItem(STORAGE_KEY) || '{}' ); }
    catch (e) { return {}; }
  }
  function saveState(state) {
    try { localStorage.setItem( STORAGE_KEY, JSON.stringify(state) ); }
    catch (e) {}
  }
  var STATE = loadState();

  /* ---------- Utils ---------- */
  function extractDirective(a) {
    if (!a) return null;
    var href = a.getAttribute('href') || '';
    try { href = new URL(href, location.href).hash || ''; } catch (e) {}
    var s = (href || '').trim().toLowerCase();
    if (s.includes('#group:')) return { kind: 'group', value: decodeURIComponent(s.split('#group:').pop()) };
    if (s.includes('#link:'))  return { kind: 'link',  value: decodeURIComponent(s.split('#link:').pop())  };
    return null;
  }
  function stripDirectiveHash(a) {
    if (!a) return;
    a.setAttribute('href', (a.getAttribute('href') || '').replace(/#(?:link|group):[^#]*$/i, '') );
  }
  function hasRealHref(a) {
    if (!a) return false;
    return !/^#/.test( a.getAttribute('href') || '' );
  }
  function normId(s) {
    return String(s || '').trim().toLowerCase()
      .replace(/\s+/g, '-').replace(/[^\w\-:.|]/g, '');
  }
  function toggleSet(li, arrow, expanded) {
    if (expanded) { li.classList.remove('is-collapsed'); arrow.setAttribute('aria-expanded','true'); }
    else          { li.classList.add('is-collapsed');    arrow.setAttribute('aria-expanded','false'); }
  }

  /* ---------- Kopf + ID ---------- */
  function makeHead(li, keepLink, containerUL, labelOverride) {
    var a = li.querySelector(':scope > a');

    var head = document.createElement('div');
    head.className = 'kr-head has-toggle'; // ganze Kopfzeile darf toggeln (außer Link)

    var btn = document.createElement('button');
    btn.type = 'button';
    btn.className = 'kr-arrow';
    btn.setAttribute('aria-expanded', 'false');
    head.appendChild(btn);

    if (keepLink && a) { stripDirectiveHash(a); head.appendChild(a); }
    else {
      var span = document.createElement('span');
      span.className = 'kr-title';
      span.textContent = labelOverride || (a && a.textContent) || '';
      head.appendChild(span);
      if (a) a.remove();
    }

    li.insertBefore(head, li.firstChild);

    // stabile ID für Persistenz
    var portlet = containerUL.closest('.vector-menu');
    var pid = (portlet && (portlet.id || portlet.getAttribute('id'))) || 'portlet';
    var labelText = (labelOverride || (head.querySelector('a, .kr-title') || {}).textContent || '').trim();
    li.dataset.krId = normId( pid + '::' + labelText );

    // Default zu; gespeicherten Zustand anwenden
    toggleSet(li, btn, false);
    var st = STATE[ li.dataset.krId ];
    if (st === 1) toggleSet(li, btn, true);

    return head;
  }

  /* ---------- Toggle-Handler ---------- */
  function handleArrowClick(btn) {
    var li = btn.closest('li.kr-group, li.kr-top');
    if (!li) return;

    var willExpand = li.classList.contains('is-collapsed');
    toggleSet(li, btn, willExpand);

    var id = li.dataset.krId;
    if (id) {
      STATE[id] = willExpand ? 1 : 0;
      saveState(STATE);
    }
  }

  /* Globale Delegation: Pfeil klickt immer; Kopfzeile klickt nur, wenn NICHT im Link */
  document.addEventListener('click', function (e) {
    var arrow = e.target && e.target.closest && e.target.closest('.kr-arrow');
    if (arrow) {
      e.preventDefault();
      handleArrowClick(arrow);
      return;
    }
    var head = e.target && e.target.closest && e.target.closest('.kr-head');
    if (head && !e.target.closest('.kr-head a')) {
      e.preventDefault(); // freie Fläche der Kopfzeile
      var btn = head.querySelector('.kr-arrow');
      if (btn) handleArrowClick(btn);
    }
  }, true); // capture=true, damit nichts dazwischenfunkt

  /* ---------- Aufbau der Gruppen ---------- */
  function buildGroups(ul) {
    var lis = Array.from( ul.querySelectorAll(':scope > li.mw-list-item') );
    var current = null;

    lis.forEach(function (li) {
      var a = li.querySelector(':scope > a');
      var d = extractDirective(a);

      if (d && d.kind === 'group') {
        var clickable = hasRealHref(a);
        li.classList.add('kr-group');
        makeHead(li, clickable, ul, (a && a.textContent) || d.value || '');
        if (!clickable && a) a.remove(); else if (clickable) stripDirectiveHash(a);

        var sub = document.createElement('ul');
        sub.className = 'kr-sub';
        li.appendChild(sub);
        current = sub;
        return;
      }

      if (current && !(d && (d.kind === 'group' || d.kind === 'link'))) {
        current.appendChild(li);
      }
    });
  }

  /* ---------- Portlets verarbeiten ---------- */
  function buildPortlets(root) {
    var lists = root.querySelectorAll('.vector-menu .vector-menu-content-list');
    lists.forEach(function (ul) {
      if (ul.dataset.krSbDone) return;
      ul.dataset.krSbDone = '1';

      var items  = Array.from( ul.querySelectorAll(':scope > li.mw-list-item') );
      var topIdx = items.findIndex(function (li) {
        var a = li.querySelector(':scope > a');
        var d = extractDirective(a);
        return d && d.kind === 'link';
      });

      if (topIdx >= 0) {
        var top = items[topIdx];
        top.classList.add('kr-top');
        makeHead(top, true, ul);
        stripDirectiveHash( top.querySelector(':scope > a') );

        var sub = document.createElement('ul');
        sub.className = 'kr-sub';
        top.appendChild(sub);

        for (var i = topIdx + 1; i < items.length; i++) sub.appendChild(items[i]);
        buildGroups(sub);
      } else {
        buildGroups(ul);
      }

      // Direktiven aus hrefs entfernen; Vector-Portlet-Heading ausblenden
      ul.querySelectorAll('a[href*="#link:"], a[href*="#group:"]').forEach(stripDirectiveHash);
      var portlet = ul.closest('.vector-menu');
      var heading = portlet && portlet.querySelector(':scope > .vector-menu-heading');
      if (heading) heading.remove();
    });
  }

  /* ---------- Bootstrapping ---------- */
  function processRoot(root) {
    if (!root || root.dataset.krSbRootDone) return;
    root.dataset.krSbRootDone = '1';
    root.classList.add('kr-sb');
    buildPortlets(root);
  }

  function init() {
    document.querySelectorAll('#mw-panel, #vector-main-menu, #vector-main-menu-pinned, .vector-drawer')
      .forEach(processRoot);
  }

  init();
  mw.hook('wikipage.content').add(init);

  // Drawer/DOM-Änderungen beobachten
  var mo = new MutationObserver(function (muts) {
    muts.forEach(function (m) {
      m.addedNodes && m.addedNodes.forEach(function (n) {
        if (!(n instanceof Element)) return;
        if (n.matches('#vector-main-menu, #vector-main-menu-pinned, .vector-drawer, #mw-panel')) processRoot(n);
        n.querySelectorAll && n.querySelectorAll('#vector-main-menu, #vector-main-menu-pinned, .vector-drawer, #mw-panel')
          .forEach(processRoot);
      });
    });
  });
  mo.observe(document.body, { childList: true, subtree: true });
})();

/* TabberNeue: Anker-Scroll unter Vector-Header + robustes „Pinning“ */
(function () {
  /* reale Sticky-Höhe (+0.5rem Luft) – korrekt für Gäste */
  function stickyOffsetPx() {
    var air = Math.round(parseFloat(getComputedStyle(document.documentElement).fontSize) * 0.5);

    // eingeloggt zuverlässig erkennen (Logout-Link existiert nur dann)
    var isLoggedIn = !!document.getElementById('pt-logout');

    // Vector 2022 Header-Container
    var hdr = document.querySelector('.vector-sticky-header-container, #vector-sticky-header');
    if (!isLoggedIn || !hdr) return air;            // Gäste: nur Luft

    // nur zählen, wenn der Header wirklich sichtbar/aktiv ist
    var cs = getComputedStyle(hdr);
    var op = parseFloat(cs.opacity || '1');
    var r  = hdr.getBoundingClientRect();
    var visible = op > 0.01 && r.top <= 0 && r.bottom > 0;

    var h = visible ? Math.round(r.height) : 0;
    if (!h && isLoggedIn) h = 50;                   // letzte Rettung nur für eingeloggte
    return h + air;
  }

  /* oberste Tabber-Leiste finden */
  function headerForPanel(panel) {
    if (!panel) return null;
    var top = panel.closest('.tabber');
    for (var cur = top; cur && cur.parentElement;) {
      var up = cur.parentElement.closest('.tabber');
      if (!up) break;
      top = up; cur = up;
    }
    return top ? top.querySelector(':scope > .tabber__header') : null;
  }

  /* Panel/Tabber, die wir beobachten wollen */
  function getObserved(tabber) {
    var section = tabber && tabber.querySelector(':scope > .tabber__section');
    return { tabber: tabber, section: section };
  }

  /* „Pinne“ den Viewport hart auf header/panel; löse erst, wenn stabil */
  function pinScrollToTarget(panelOrHeader) {
    var target = panelOrHeader;
    if (!target) return;

    var tabber = target.closest('.tabber');
    var obs = getObserved(tabber);

    /* Freeze: Höhe zum Start lesen und auf dem Container festhalten */
    if (tabber && obs.section) {
      var h0 = Math.ceil(obs.section.getBoundingClientRect().height);
      tabber.style.setProperty('--tabber-freeze-h', h0 + 'px');
      tabber.classList.add('tabber-is-switching');
    }

    /* Smooth-Scrolling global aus, damit nichts „nachfedert“ */
    document.documentElement.classList.add('fs-scroll-now');

    /* Fremde scrollIntoView()-Aufrufe währenddessen blocken */
    var origScrollIntoView = Element.prototype.scrollIntoView;
    Element.prototype.scrollIntoView = function(){ /* noop while pinned */ };

    /* Beobachte Größenänderungen → Stability Counter zurücksetzen */
    var stable = 0, lastH = -1, raf = 0;
    var ro = null;
    if (obs.section && window.ResizeObserver) {
      ro = new ResizeObserver(function(){ stable = 0; });
      try { ro.observe(obs.section); } catch(e){}
    }

    var MAX_MS = 3000;
    var t0 = performance.now();

    function wantY() {
      return target.getBoundingClientRect().top + window.scrollY - stickyOffsetPx();
    }

    function release() {
      if (ro) ro.disconnect();
      Element.prototype.scrollIntoView = origScrollIntoView;
      document.documentElement.classList.remove('fs-scroll-now');
      if (tabber) {
        tabber.classList.remove('tabber-is-switching');
        tabber.style.removeProperty('--tabber-freeze-h');
      }
      cancelAnimationFrame(raf);
    }

    function tick() {
      var y = wantY();
      if (Math.abs(window.scrollY - y) > 1) {
        /* Hart auf die Ziel-Position setzen */
        window.scrollTo(0, y);
        stable = 0; /* wir mussten korrigieren → noch nicht stabil */
      }

      /* aktuelles Maß nehmen (Section bevorzugt, sonst Tabber) */
      var hNow = 0;
      if (obs.section) hNow = obs.section.getBoundingClientRect().height;
      else if (tabber)  hNow = tabber.getBoundingClientRect().height;

      if (Math.abs(hNow - lastH) < 1 && Math.abs(window.scrollY - y) <= 1) {
        stable++;
      } else {
        stable = 0;
        lastH = hNow;
      }

      /* 12 stabile Frames ≈ ~200ms bei 60fps */
      if (stable >= 12 || (performance.now() - t0) > MAX_MS) {
        release();
        return;
      }
      raf = requestAnimationFrame(tick);
    }

    /* sofort starten */
    raf = requestAnimationFrame(tick);
  }

  function scrollToTab(hashOrId) {
    var name = (hashOrId || '').replace(/^#/, '');
    if (!/^tabber-/.test(name)) return;

    var panel  = document.getElementById(name);
    if (!panel) return;

    var header = headerForPanel(panel) || panel;
    pinScrollToTarget(header);
  }

  /* 1) Direkt mit Hash geladen */
  if (/^#tabber-/.test(location.hash)) {
    setTimeout(function(){ scrollToTab(location.hash); }, 60);
  }

  /* 2) Hashwechsel */
  window.addEventListener('hashchange', function () {
    if (/^#tabber-/.test(location.hash)) scrollToTab(location.hash);
  });

  /* 3) Klicks (TabberNeue nutzt History API, verhindert default) */
  document.addEventListener('click', function (e) {
    var a = e.target && e.target.closest('a[href^="#tabber-"], button[aria-controls^="tabber-"]');
    if (!a) return;
    var id = a.getAttribute('href') || ('#' + a.getAttribute('aria-controls'));
    setTimeout(function(){ scrollToTab(id); }, 60);
  }, true);
})();