Glossar – Konzept & Richtlinien
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.