Glossar – Konzept & Richtlinien

Glossar – Konzept & Richtlinien

Konzept · Version v1.1 · 04.11.2025

1. Zweck

Ein zentrales, wachsendes Glossar sorgt für klare Begriffe. Artikel dürfen Fachwörter nutzen – sie verlinken in einfacher Sprache ins Glossar.

2. Namenskonventionen (verbindlich)

  • Sichtbare Labels: dürfen Umlaute behalten (z. B. „Präzision“).
  • Anker‑IDs: immer ASCII (ä→ae, ö→oe, ü→ue, ß→ss), keine Leerzeichen.
  • Alt‑Alias: zusätzliche <span id="…"> für historische oder fehlerhafte IDs (z. B. prazision, koharenz).

3. Struktur

  • Index 0–9 & A–Z direkt unter dem H1.
  • Abschnitte je Buchstabe: <h2>A</h2><dl>…</dl>.
  • Einträge als Paare: <dt id="…" >Label</dt> + <dd>Definition</dd> (kein Fett im <dd>).

4. Pflege

  • Additiv: Nichts löschen; bestehende Einträge verfeinern.
  • Keine Duplikate: Zusammenführen auf einen ASCII‑Anker; Altformen via Alias‑Span.
  • Version & Delta‑Log: vor jedem Import erhöhen.

5. Verlinkung

Links aus Artikeln: <a class="gpop" href="/glossar/#praezision">Präzision</a> (öffnet im neuen Tab, Popup auf Hover/Focus).

6. MU‑Plugin (v1.1.1)

Pfad: /wp-content/mu-plugins/hbw-glossar-popups_v1.1.1.php. Quelle:

<?php
/**
 * Plugin Name: HBW Glossar Popups (MU)
 * Description: Tooltip-Popups für Glossarbegriffe via <a class="gpop"> (mit data-def oder Lazy-Fetch aus /glossar/#id). Single-file MU-Plugin.
 * Version: 1.1.1
 * Author: Clara · HBW
 * Requires at least: 5.6
 * Requires PHP: 7.4
 *
 * Ladeort: /wp-content/mu-plugins/hbw-glossar-popups.php
 * Hinweis: MU-Plugins sind immer aktiv und updatesicher. Dieses File enthält CSS/JS inline (keine Assets nötig).
 *
 * Änderungslog:
 * - 2025-11-03 · v1.1.0 · Popup fixiert sich an der Cursor-Position (kein Follow), stabilere Kanten-Logik, Hover/Focus/Touch, sanftes Close.
 * - 2025-11-03 · v1.0.1 · NOWDOC für CSS/JS; Guards gegen Admin/AJAX/JSON.
 * - 2025-11-03 · v1.0.0 · Initiale Version.
 */

if ( ! defined( 'ABSPATH' ) ) exit;

define( 'HBW_GPOP_VERSION', '1.1.0' );

/**
 * Filter (optional überschreiben):
 *  - hbw_gpop_is_enabled : bool
 *  - hbw_gpop_glossary_url : string
 *  - hbw_gpop_selector : string
 */
function hbw_gpop_enabled() { return (bool) apply_filters( 'hbw_gpop_is_enabled', true ); }
function hbw_gpop_config() {
  return array(
    'glossaryUrl' => apply_filters( 'hbw_gpop_glossary_url', home_url( '/glossar/' ) ),
    'selector'    => apply_filters( 'hbw_gpop_selector', 'a.gpop' ),
  );
}

add_action( 'wp_enqueue_scripts', function() {
  if ( is_admin() || wp_doing_ajax() || wp_is_json_request() || ! hbw_gpop_enabled() ) return;

  $css = <<<'CSS'
.gpop{position:relative;text-decoration:underline dotted;cursor:help}
.gpop-pop{position:fixed;max-width:28rem;padding:.6rem .8rem;border:1px solid rgba(0,0,0,.15);
background:#fff;box-shadow:0 6px 24px rgba(0,0,0,.15);border-radius:.6rem;z-index:9999;font-size:.95em;line-height:1.35;pointer-events:auto}
.gpop-pop h4{margin:.1rem 0 .3rem;font-size:1em}
.gpop-pop .src{margin-top:.4rem;font-size:.85em;opacity:.75}
.gpop-pop.hidden{display:none}
@media (prefers-color-scheme: dark){
  .gpop-pop{background:#111;color:#eee;border-color:#333;box-shadow:0 6px 24px rgba(0,0,0,.6)}
}
CSS;

  $js = <<<'JS'
(()=>{
  const CFG = (window.hbwGpopCfg || {glossaryUrl:'/glossar/', selector:'a.gpop'});
  let pop=null, cache={}, currentAnchor=null, hideTimer=null;

  function ensure(){
    if(pop) return;
    pop=document.createElement('div');
    pop.className='gpop-pop hidden';
    document.body.appendChild(pop);
    pop.addEventListener('mouseenter', ()=>{ if(hideTimer){ clearTimeout(hideTimer); hideTimer=null; } });
    pop.addEventListener('mouseleave', tryHideSoon);
  }

  function build(term,def,href){
    const src = href ? `<div class="src"><a href="${href}">Mehr im Glossar</a></div>` : '';
    return `<h4>${term}</h4><div>${def||'–'}</div>${src}`;
  }

  function placeAt(x,y){
    const pad=10;
    pop.style.left = (x+pad)+'px';
    pop.style.top  = (y+pad)+'px';
    // Kantenkorrektur nach dem Rendern
    const r=pop.getBoundingClientRect();
    let left=parseFloat(pop.style.left), top=parseFloat(pop.style.top);
    if(r.right>window.innerWidth-8) left=Math.max(8, window.innerWidth - r.width - 8);
    if(r.bottom>window.innerHeight-8) top=Math.max(8, window.innerHeight - r.height - 8);
    pop.style.left = left+'px';
    pop.style.top  = top+'px';
  }

  function showFreeze(e, html){
    ensure();
    // Initial an Cursor setzen, dann nicht mehr bewegen
    const t=(e.touches&&e.touches[0])||null;
    const x=t? t.clientX : (e.clientX||0);
    const y=t? t.clientY : (e.clientY||0);
    pop.innerHTML = html;
    pop.classList.remove('hidden');
    placeAt(x,y);
  }

  function hideNow(){ if(pop){ pop.classList.add('hidden'); } currentAnchor=null; }
  function tryHideSoon(){ if(hideTimer){ clearTimeout(hideTimer); } hideTimer=setTimeout(()=>{ hideNow(); }, 120); }

  async function getDef(anchor){
    const term = anchor.textContent.trim();
    const href = anchor.getAttribute('href')||'';
    const id = href.includes('#') ? href.split('#').pop() : null;
    let def = anchor.getAttribute('data-def');
    if((!def || def.length<4) && id){
      try{
        if(cache[id]) def=cache[id];
        else {
          const res = await fetch(CFG.glossaryUrl, {credentials:'same-origin'});
          const html = await res.text();
          const tmp = document.createElement('div'); tmp.innerHTML=html;
          const dt = tmp.querySelector('dt#'+CSS.escape(id));
          const dd = dt? dt.nextElementSibling : null;
          def = dd? dd.textContent.trim() : '';
          cache[id]=def;
        }
      }catch{}
    }
    if(!def) def = 'Kurzdefinition folgt.';
    return {term, def, href};
  }

  function wantAnchor(target){
    return target && target.closest && target.closest(CFG.selector);
  }

  function onEnter(e){
    const a = wantAnchor(e.target);
    if(!a) return;
    if(hideTimer){ clearTimeout(hideTimer); hideTimer=null; }
    currentAnchor=a;
    getDef(a).then(({term,def,href})=>{
      if(currentAnchor!==a) return; // Anker gewechselt
      showFreeze(e, build(term,def,href));
    });
  }

  function onLeave(e){
    // Nur schließen, wenn weder Anker noch Popup unter dem Zeiger/Fokus sind
    const goingTo = e.relatedTarget;
    if(goingTo && (goingTo.closest && (goingTo.closest(CFG.selector) || goingTo.closest('.gpop-pop')))) return;
    tryHideSoon();
  }

  // Events: NICHT mousemove – nur enter/focus/touch, damit die Position stabil bleibt
  document.addEventListener('mouseover', onEnter, {passive:true});
  document.addEventListener('focusin', onEnter);
  document.addEventListener('mouseout', onLeave, {passive:true});
  document.addEventListener('focusout', onLeave);
  document.addEventListener('touchstart', (e)=>{ const a=wantAnchor(e.target); if(a){ onEnter(e); } }, {passive:true});
  document.addEventListener('keydown', (e)=>{ if(e.key==='Escape') hideNow(); });

})();
JS;

  wp_register_style( 'hbw-gpop-inline', false, array(), HBW_GPOP_VERSION );
  wp_enqueue_style( 'hbw-gpop-inline' );
  wp_add_inline_style( 'hbw-gpop-inline', $css );

  wp_register_script( 'hbw-gpop-inline', '', array(), HBW_GPOP_VERSION, true );
  wp_enqueue_script( 'hbw-gpop-inline' );

  $cfg = hbw_gpop_config();
  $json = wp_json_encode( $cfg );
  wp_add_inline_script( 'hbw-gpop-inline', 'window.hbwGpopCfg = ' . $json . ';', 'before' );
  wp_add_inline_script( 'hbw-gpop-inline', $js );
}, 20 );


/* HBW GP v1.1.1 inline JS */

(function(){
  if (document.documentElement.classList.contains('hbw-gp-ready')) return;
  document.documentElement.classList.add('hbw-gp-ready');
  function toAscii(s){
    if(!s) return '';
    return s.replace(/Ä/g,'Ae').replace(/Ö/g,'Oe').replace(/Ü/g,'Ue')
            .replace(/ä/g,'ae').replace(/ö/g,'oe').replace(/ü/g,'ue')
            .replace(/ß/g,'ss').replace(/s+/g,'').replace(/[^A-Za-z0-9-_]/g,'');
  }
  function normHref(a){
    var h = a.getAttribute('href')||'';
    var m = h.match(/^(.*/glossar/#)([^#s]+)$/i);
    if(!m) return null;
    var base = m[1], raw = decodeURIComponent(m[2]||''), norm = toAscii(raw);
    var fixed = base + norm;
    if (fixed !== h) a.setAttribute('href', fixed);
    a.setAttribute('target','_blank'); a.setAttribute('rel','noopener');
    return norm;
  }

  var POP=null, CACHE=null, LOADING=false, Q=[];
  function ensurePopup(){
    if(POP) return POP;
    POP = document.createElement('div');
    POP.className = 'hbw-gp121 is-hidden';
    POP.setAttribute('role','dialog'); POP.setAttribute('aria-live','polite');
    document.body.appendChild(POP);
    return POP;
  }
  function placeAt(x,y){
    var pad=12, w = POP.offsetWidth||280, h = POP.offsetHeight||120;
    var vw = window.innerWidth, vh = window.innerHeight;
    var nx = Math.min(Math.max(x+pad, 8), vw - w - 8);
    var ny = Math.min(Math.max(y+pad, 8), vh - h - 8);
    POP.style.position='fixed'; POP.style.left=nx+'px'; POP.style.top=ny+'px';
  }
  function showAt(html, x, y){
    ensurePopup();
    POP.innerHTML = html;
    POP.classList.remove('is-hidden'); POP.classList.add('is-visible');
    placeAt(x,y);
  }
  function hide(){ if(POP){ P

7. Qualität

  • Definitionen kurz, klar, anschlussfähig (max. ~3 Sätze); Tiefe in Artikeln.
  • Du‑Sprache, alltagstaugliche Beispiele, keine Heilsversprechen.