<?php
/**
 * Glossar-Mindmap — Force-directed Graph aller Begriffe + Related-Verbindungen.
 *
 * Datenquelle: /php/api/glossar.php?graph=1
 * Modul-Metadaten: DB-Tabelle module_info (Admin-pflegbar)
 */
require_once __DIR__ . '/../php/config/app.php';
require_once __DIR__ . '/../php/config/db.php';

$bp  = BASE_PATH;
$api = BASE_URL . '/php/api/glossar.php';

// Farben sind Design-Entscheidung — nicht in DB, hier pro Modul gemappt.
$moduleColors = [
    'klima'     => '#4a7c8a',
    'fluss'     => '#5a8a5e',
    'heli'      => '#e8833a',
    'stadt'     => '#c85c4a',
    'regenwald' => '#3a6b3e',
    'erdbeben'  => '#8a6a4a',
    'energiemix'=> '#c4a97a',
    'lieferketten'=> '#b07aa1',
];
$defaultColor = '#c4a97a';

// Icons + Titel aus module_info (Admin-pflegbar)
$modDb = getDB()->query("
    SELECT module_id, title, icon, sort_order
    FROM module_info
    WHERE status IN ('aktiv', 'beta')
    ORDER BY sort_order, title
")->fetchAll(PDO::FETCH_ASSOC);

$moduleMeta = [];
foreach ($modDb as $m) {
    $moduleMeta[$m['module_id']] = [
        'icon'  => $m['icon'] ?: '📘',
        'label' => $m['title'],
        'color' => $moduleColors[$m['module_id']] ?? $defaultColor,
    ];
}
?><!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Glossar geograsim.at — Mindmap</title>
<meta name="description" content="Interaktive Mindmap aller Glossar-Begriffe mit ihren Verbindungen. Navigation per Klick, Drag und Zoom.">
<link rel="icon" type="image/svg+xml" href="<?= $bp ?>/favicon.svg">
<link rel="icon" type="image/png" href="<?= $bp ?>/favicon-96x96.png" sizes="96x96">
<link rel="stylesheet" href="<?= $bp ?>/assets/fonts/inter.css">
<link rel="stylesheet" href="<?= $bp ?>/assets/css/design-system.css">
<style>
  html, body { height: 100%; background: var(--ggs-bg); overflow: hidden; }
  body { display: flex; flex-direction: column; }

  /* ===== Topbar ===== */
  .mm-topbar {
    background: var(--ggs-white);
    padding: .55rem 1.2rem;
    display: flex; align-items: center; gap: .8rem;
    border-bottom: 1px solid var(--ggs-border);
    flex-shrink: 0; z-index: 50;
  }
  .mm-topbar a { text-decoration: none; }
  .mm-topbar-brand { display: flex; align-items: center; }
  .mm-topbar-brand img { height: 40px; display: block; }
  .mm-topbar .spacer { flex: 1; }
  .mm-btn-sm {
    display: inline-flex; align-items: center; gap: .3rem;
    min-height: 40px; padding: .4rem .8rem;
    border: 1px solid var(--ggs-border);
    border-radius: var(--ggs-radius-sm); background: var(--ggs-white);
    font-size: .78rem; font-weight: 600; cursor: pointer;
    color: var(--ggs-text); font-family: inherit;
    text-decoration: none;
  }
  .mm-btn-sm.primary { background: var(--ggs-fjord); color: var(--ggs-white); border-color: var(--ggs-fjord); }
  @media (hover: hover) {
    .mm-btn-sm:hover { background: var(--ggs-fjord-light); color: var(--ggs-fjord-dark); border-color: var(--ggs-fjord); }
    .mm-btn-sm.primary:hover { background: var(--ggs-fjord-dark); }
  }

  /* ===== Viewport ===== */
  .mm-viewport {
    flex: 1; position: relative; overflow: hidden;
    background:
      radial-gradient(circle at 30% 20%, rgba(74,124,138,.05), transparent 60%),
      radial-gradient(circle at 70% 75%, rgba(90,138,94,.06), transparent 60%),
      var(--ggs-bg);
  }
  .mm-svg {
    width: 100%; height: 100%; display: block;
    cursor: grab; touch-action: none;
  }
  .mm-svg.dragging { cursor: grabbing; }

  .mm-edge {
    stroke: #9fb9c1; stroke-width: 1; fill: none;
    transition: stroke-opacity .2s;
    stroke-opacity: .42;
  }
  .mm-edge.hi   { stroke: var(--ggs-fjord-dark); stroke-opacity: .95; stroke-width: 2.2; }
  .mm-edge.dim  { stroke-opacity: .1; }

  .mm-node circle {
    stroke: var(--ggs-white); stroke-width: 2.5; cursor: pointer;
    transition: r .2s, stroke-width .2s;
  }
  .mm-node text {
    font-family: var(--ggs-font);
    font-size: 11px; font-weight: 700; fill: var(--ggs-fjord-dark);
    text-anchor: middle; pointer-events: none;
    paint-order: stroke; stroke: var(--ggs-bg); stroke-width: 3;
  }
  .mm-node.hi circle { stroke-width: 4; }
  .mm-node.hi text   { font-size: 12.5px; }
  .mm-node.dim circle { opacity: .3; }
  .mm-node.dim text   { opacity: .3; }
  .mm-node.has-img circle { stroke: var(--ggs-moss); stroke-dasharray: 3 2; }

  /* ===== Overlay-Panels ===== */
  .mm-panel {
    position: absolute; background: var(--ggs-white);
    border: 1px solid var(--ggs-border); border-radius: var(--ggs-radius-md);
    box-shadow: var(--ggs-shadow-md); padding: .9rem 1rem;
    font-size: .82rem; z-index: 10;
  }
  /* Info & Suche links oben */
  .mm-info { top: 14px; left: 14px; width: 280px; }
  .mm-info h2 { font-size: 1.05rem; font-weight: 900; color: var(--ggs-fjord-dark); margin-bottom: .3rem; }
  .mm-info p { font-size: .75rem; color: var(--ggs-text-muted); line-height: 1.5; margin-bottom: .6rem; }
  .mm-search {
    width: 100%; padding: .45rem .7rem; border: 1.5px solid var(--ggs-border);
    border-radius: var(--ggs-radius-sm); font-family: inherit; font-size: .82rem;
    outline: none; transition: border-color .15s;
  }
  .mm-search:focus { border-color: var(--ggs-fjord); }
  .mm-stats { font-size: .7rem; color: var(--ggs-text-muted); margin-top: .5rem; }
  .mm-stats strong { color: var(--ggs-fjord-dark); font-weight: 700; }

  /* Legende rechts oben */
  .mm-legend { top: 14px; right: 14px; min-width: 200px; }
  .mm-legend h3 { font-size: .72rem; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; color: var(--ggs-text-muted); margin-bottom: .5rem; }
  .mm-legend-row { display: flex; align-items: center; gap: .4rem; font-size: .78rem; margin-bottom: .3rem; color: var(--ggs-text); }
  .mm-legend-dot { width: 14px; height: 14px; border-radius: 50%; flex-shrink: 0; }
  .mm-legend-emoji { font-size: 1rem; line-height: 1; width: 18px; text-align: center; }
  .mm-legend-hint { font-size: .68rem; color: var(--ggs-text-muted); margin-top: .6rem; border-top: 1px solid var(--ggs-border); padding-top: .5rem; line-height: 1.45; }

  /* Zoom-Buttons rechts unten */
  .mm-zoom { bottom: 14px; right: 14px; display: flex; flex-direction: column; gap: .3rem; padding: .3rem; }
  .mm-zoom button {
    width: 36px; height: 36px; border: 0; background: var(--ggs-bg);
    border-radius: var(--ggs-radius-sm); font-size: 1.15rem; font-weight: 800;
    color: var(--ggs-fjord-dark); cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    transition: background .15s;
  }
  @media (hover: hover) { .mm-zoom button:hover { background: var(--ggs-fjord-light); } }

  /* Node-Popover */
  .mm-pop {
    position: absolute; width: 300px; z-index: 30;
    background: var(--ggs-white); border: 1px solid var(--ggs-border);
    border-radius: var(--ggs-radius-md); box-shadow: var(--ggs-shadow-lg);
    padding: 1rem; pointer-events: auto; display: none;
  }
  .mm-pop.open { display: block; }
  .mm-pop-close {
    position: absolute; top: 6px; right: 6px; width: 24px; height: 24px;
    border: 0; background: transparent; cursor: pointer;
    color: var(--ggs-text-muted); font-size: 1.1rem;
    border-radius: 50%;
  }
  @media (hover: hover) { .mm-pop-close:hover { background: var(--ggs-coral-light); color: var(--ggs-coral); } }
  .mm-pop-cat {
    display: inline-block; font-size: .62rem; font-weight: 800;
    text-transform: uppercase; letter-spacing: .06em;
    color: var(--ggs-fjord); background: var(--ggs-fjord-light);
    padding: 2px 8px; border-radius: var(--ggs-radius-pill); margin-bottom: .3rem;
  }
  .mm-pop-title { font-size: 1.05rem; font-weight: 900; color: var(--ggs-fjord-dark); margin-bottom: .3rem; padding-right: 26px; }
  .mm-pop-short { font-size: .85rem; color: var(--ggs-text); line-height: 1.5; margin-bottom: .7rem; }
  .mm-pop-mods { display: flex; gap: .25rem; margin-bottom: .7rem; }
  .mm-pop-mod {
    font-size: .65rem; padding: 2px 8px;
    background: var(--ggs-bg); border-radius: var(--ggs-radius-pill);
    color: var(--ggs-text-muted); font-weight: 600;
  }
  .mm-pop-actions { display: flex; flex-direction: column; gap: .3rem; }
  .mm-pop-btn {
    display: block; padding: .55rem .8rem; border-radius: var(--ggs-radius-sm);
    font-size: .82rem; font-weight: 700; text-decoration: none; text-align: left;
    border: 1.5px solid var(--ggs-border); color: var(--ggs-fjord-dark);
    background: var(--ggs-white); transition: all .15s;
    font-family: inherit; cursor: pointer;
  }
  .mm-pop-btn.primary { background: var(--ggs-fjord); color: var(--ggs-white); border-color: var(--ggs-fjord); }
  @media (hover: hover) {
    .mm-pop-btn:hover { border-color: var(--ggs-fjord); background: var(--ggs-fjord-light); color: var(--ggs-fjord); }
    .mm-pop-btn.primary:hover { background: var(--ggs-fjord-dark); border-color: var(--ggs-fjord-dark); color: var(--ggs-white); }
  }
  .mm-pop-btn small { display: block; font-size: .68rem; font-weight: 500; opacity: .75; margin-top: 1px; }
  .mm-pop-related { margin-top: .5rem; border-top: 1px solid var(--ggs-border); padding-top: .6rem; }
  .mm-pop-related-title { font-size: .65rem; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; color: var(--ggs-text-muted); margin-bottom: .3rem; }
  .mm-pop-related-list { display: flex; flex-wrap: wrap; gap: .25rem; }
  .mm-pop-related-list button {
    font-size: .72rem; padding: 2px 8px; border: 1px solid var(--ggs-border);
    background: var(--ggs-white); border-radius: var(--ggs-radius-pill);
    color: var(--ggs-fjord-dark); cursor: pointer; font-family: inherit;
  }
  @media (hover: hover) { .mm-pop-related-list button:hover { background: var(--ggs-fjord-light); border-color: var(--ggs-fjord); } }

  /* Loading */
  .mm-loading {
    position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
    color: var(--ggs-text-muted); font-size: .9rem; background: var(--ggs-bg);
  }

  @media (max-width: 700px) {
    .mm-info, .mm-legend { display: none; }
    .mm-pop { width: calc(100vw - 28px); }
  }
</style>
</head>
<body>

<div class="mm-topbar">
  <a href="<?= $bp ?>/index.html" class="mm-topbar-brand">
    <img src="<?= $bp ?>/assets/img/geograsim_t.svg" alt="GeoGraSim">
  </a>
  <span class="spacer"></span>
  <a href="<?= $bp ?>/pages/glossar.php" class="mm-btn-sm">📖 Glossar-Liste</a>
  <a href="<?= $bp ?>/index.html" class="mm-btn-sm">Startseite</a>
  <a href="<?= $bp ?>/handbuch.html" class="mm-btn-sm">Handbuch</a>
  <a href="<?= $bp ?>/login.html" class="mm-btn-sm primary">Anmelden</a>
</div>

<div class="mm-viewport" id="mm-viewport">
  <div class="mm-loading" id="mm-loading">Mindmap wird aufgebaut …</div>

  <svg class="mm-svg" id="mm-svg" xmlns="http://www.w3.org/2000/svg">
    <g id="mm-scene">
      <g id="mm-edges"></g>
      <g id="mm-nodes"></g>
    </g>
  </svg>

  <!-- Info-Panel links -->
  <div class="mm-panel mm-info">
    <h2>🧠 Glossar-Mindmap</h2>
    <p>Alle Begriffe und ihre Zusammenhänge auf einen Blick. Linien verbinden verwandte Begriffe.</p>
    <input id="mm-search" class="mm-search" type="search" placeholder="Begriff suchen …" autocomplete="off">
    <div class="mm-stats" id="mm-stats"></div>
  </div>

  <!-- Legende rechts -->
  <div class="mm-panel mm-legend">
    <h3>Module</h3>
    <?php foreach ($moduleMeta as $id => $m): ?>
      <div class="mm-legend-row" data-module="<?= htmlspecialchars($id) ?>">
        <span class="mm-legend-dot" style="background:<?= $m['color'] ?>"></span>
        <span class="mm-legend-emoji"><?= $m['icon'] ?></span><?= htmlspecialchars($m['label']) ?>
      </div>
    <?php endforeach; ?>
    <div class="mm-legend-row" data-module="__default__">
      <span class="mm-legend-dot" style="background:<?= $defaultColor ?>"></span>
      <span class="mm-legend-emoji">📘</span>sonstige · Einheiten
    </div>
    <div class="mm-legend-hint">
      🖱 Ziehen, um Knoten zu bewegen<br>
      🔍 Scrollen zum Zoomen<br>
      🖐 Leere Fläche ziehen zum Verschieben<br>
      🌿 Gestrichelter Rand = Grafik vorhanden
    </div>
  </div>

  <!-- Zoom-Buttons -->
  <div class="mm-panel mm-zoom">
    <button id="mm-zoom-in"  title="Hineinzoomen">+</button>
    <button id="mm-zoom-out" title="Herauszoomen">−</button>
    <button id="mm-zoom-fit" title="Alles einpassen">⛶</button>
  </div>

  <!-- Node-Popover -->
  <div class="mm-pop" id="mm-pop">
    <button class="mm-pop-close" id="mm-pop-close" aria-label="Schließen">×</button>
    <div id="mm-pop-body"></div>
  </div>
</div>

<script>
const API   = <?= json_encode($api) ?>;
const BP    = <?= json_encode($bp) ?>;
const PAGES = <?= json_encode(BASE_URL . '/pages') ?>;

// Modul-Metadaten aus DB (module_info): icon, label, color pro Modul
const MODULE_META  = <?= json_encode($moduleMeta, JSON_UNESCAPED_UNICODE) ?>;
const DEFAULT_COLOR = <?= json_encode($defaultColor) ?>;
const CATEGORY_LABEL = { klima:'Klima', einheit:'Einheit', energie:'Energie', stadt:'Stadt', wasser:'Wasser', geologie:'Geologie', wirtschaft:'Wirtschaft', biologie:'Biologie' };

function modIcon(m)  { return (MODULE_META[m]?.icon) || '•'; }
function modLabel(m) { return (MODULE_META[m]?.label) || m; }
function modColor(m) { return (MODULE_META[m]?.color) || DEFAULT_COLOR; }

function esc(s) { return String(s ?? '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); }
function primaryColor(modules) {
  if (!modules || !modules.length) return DEFAULT_COLOR;
  // Priorität: klima > stadt > fluss > heli > regenwald > erster Treffer
  for (const m of ['klima','stadt','fluss','heli','regenwald']) {
    if (modules.includes(m) && MODULE_META[m]) return MODULE_META[m].color;
  }
  for (const m of modules) if (MODULE_META[m]) return MODULE_META[m].color;
  return DEFAULT_COLOR;
}

// ============ STATE ============
const STATE = {
  nodes: [],   // {id, key_slug, title, short, category, modules, image_path, x, y, vx, vy, fixed}
  edges: [],   // {a, b}   IDs
  byId: new Map(),
  edgeSet: new Map(), // nodeId → Set of connected node IDs
  zoom: 1, panX: 0, panY: 0,
  dragging: null,      // node being dragged
  panStart: null,      // {x,y,panX,panY} during viewport pan
  width: 0, height: 0,
  highlightId: null
};

// ============ LOAD ============
async function load() {
  const res = await fetch(API + '?graph=1');
  const data = await res.json();
  STATE.nodes = data.nodes.map(n => ({
    ...n,
    x: 0, y: 0, vx: 0, vy: 0, fixed: false
  }));
  STATE.edges = data.edges;
  STATE.byId = new Map(STATE.nodes.map(n => [n.id, n]));
  STATE.edgeSet = new Map();
  STATE.nodes.forEach(n => STATE.edgeSet.set(n.id, new Set()));
  STATE.edges.forEach(e => {
    if (STATE.edgeSet.has(e.a)) STATE.edgeSet.get(e.a).add(e.b);
    if (STATE.edgeSet.has(e.b)) STATE.edgeSet.get(e.b).add(e.a);
  });

  // Viewport-Dimensionen
  resize();

  // Initial-Positionen: Kreis um Mitte
  const cx = STATE.width / 2, cy = STATE.height / 2;
  const r = Math.min(STATE.width, STATE.height) * 0.35;
  STATE.nodes.forEach((n, i) => {
    const a = (i / STATE.nodes.length) * Math.PI * 2;
    n.x = cx + Math.cos(a) * r;
    n.y = cy + Math.sin(a) * r;
  });

  document.getElementById('mm-loading').style.display = 'none';
  document.getElementById('mm-stats').innerHTML =
    `<strong>${STATE.nodes.length}</strong> Begriffe · <strong>${STATE.edges.length}</strong> Verbindungen`;

  buildGraph();
  // Force-Simulation: viele Ticks initial, dann kontinuierlich
  for (let i = 0; i < 400; i++) simulate(0.92);
  render();
  // Soft-Simulation weiter laufen lassen für stabiles Einschwingen
  startAnim();
}

// ============ FORCE SIMULATION ============
function simulate(damping = 0.88) {
  const N = STATE.nodes.length;
  const K_REPEL  = 18000;   // Abstoßung zwischen allen Knoten
  const K_SPRING = 0.012;   // Feder-Attraktion auf Kanten
  const L0       = 140;     // Ziellänge der Federn
  const K_GRAV   = 0.003;   // Zentrumsanziehung
  const cx = STATE.width / 2, cy = STATE.height / 2;

  // Kräfte auf Null setzen
  for (const n of STATE.nodes) { n.fx = 0; n.fy = 0; }

  // Abstoßung (O(n²) — bei 25 Knoten kein Problem)
  for (let i = 0; i < N; i++) {
    const ni = STATE.nodes[i];
    for (let j = i + 1; j < N; j++) {
      const nj = STATE.nodes[j];
      let dx = ni.x - nj.x, dy = ni.y - nj.y;
      let d2 = dx*dx + dy*dy;
      if (d2 < 1) { d2 = 1; dx = Math.random()-.5; dy = Math.random()-.5; }
      const f = K_REPEL / d2;
      const d = Math.sqrt(d2);
      const fx = f * dx / d, fy = f * dy / d;
      ni.fx += fx; ni.fy += fy;
      nj.fx -= fx; nj.fy -= fy;
    }
  }

  // Feder-Anziehung entlang Kanten
  for (const e of STATE.edges) {
    const a = STATE.byId.get(e.a), b = STATE.byId.get(e.b);
    if (!a || !b) continue;
    const dx = b.x - a.x, dy = b.y - a.y;
    const d = Math.sqrt(dx*dx + dy*dy) || 1;
    const f = K_SPRING * (d - L0);
    const fx = f * dx / d, fy = f * dy / d;
    a.fx += fx; a.fy += fy;
    b.fx -= fx; b.fy -= fy;
  }

  // Zentrumsanziehung
  for (const n of STATE.nodes) {
    n.fx += (cx - n.x) * K_GRAV;
    n.fy += (cy - n.y) * K_GRAV;
  }

  // Position updaten
  for (const n of STATE.nodes) {
    if (n.fixed) { n.vx = 0; n.vy = 0; continue; }
    n.vx = (n.vx + n.fx) * damping;
    n.vy = (n.vy + n.fy) * damping;
    // Geschwindigkeit cappen (stabiler)
    const v = Math.sqrt(n.vx*n.vx + n.vy*n.vy);
    if (v > 20) { n.vx *= 20/v; n.vy *= 20/v; }
    n.x += n.vx;
    n.y += n.vy;
  }
}

// ============ RENDER ============
const SVGNS = 'http://www.w3.org/2000/svg';

function buildGraph() {
  const edgesG = document.getElementById('mm-edges');
  const nodesG = document.getElementById('mm-nodes');
  edgesG.innerHTML = '';
  nodesG.innerHTML = '';

  // Edges
  for (const e of STATE.edges) {
    const line = document.createElementNS(SVGNS, 'line');
    line.setAttribute('class', 'mm-edge');
    line.dataset.a = e.a;
    line.dataset.b = e.b;
    edgesG.appendChild(line);
  }

  // Nodes
  for (const n of STATE.nodes) {
    const g = document.createElementNS(SVGNS, 'g');
    g.setAttribute('class', 'mm-node' + (n.image_path ? ' has-img' : ''));
    g.dataset.id = n.id;

    const circle = document.createElementNS(SVGNS, 'circle');
    circle.setAttribute('r', nodeRadius(n));
    circle.setAttribute('fill', primaryColor(n.modules));

    const text = document.createElementNS(SVGNS, 'text');
    text.setAttribute('dy', nodeRadius(n) + 15);
    text.textContent = n.title.length > 22 ? n.title.slice(0, 20) + '…' : n.title;

    g.appendChild(circle);
    g.appendChild(text);
    nodesG.appendChild(g);

    // Drag
    g.addEventListener('pointerdown', ev => startDrag(ev, n, g));
    g.addEventListener('click', ev => { ev.stopPropagation(); openPopover(n, g); });
    g.addEventListener('pointerenter', () => hoverNode(n.id));
    g.addEventListener('pointerleave', () => hoverNode(null));
  }
}

function nodeRadius(n) {
  const deg = STATE.edgeSet.get(n.id)?.size || 0;
  return 10 + Math.min(deg, 8) * 1.5;
}

function render() {
  const edgesG = document.getElementById('mm-edges');
  const nodesG = document.getElementById('mm-nodes');

  for (const line of edgesG.children) {
    const a = STATE.byId.get(+line.dataset.a);
    const b = STATE.byId.get(+line.dataset.b);
    if (!a || !b) continue;
    line.setAttribute('x1', a.x);
    line.setAttribute('y1', a.y);
    line.setAttribute('x2', b.x);
    line.setAttribute('y2', b.y);
  }
  for (const g of nodesG.children) {
    const n = STATE.byId.get(+g.dataset.id);
    if (!n) continue;
    g.setAttribute('transform', `translate(${n.x}, ${n.y})`);
  }

  document.getElementById('mm-scene').setAttribute(
    'transform',
    `translate(${STATE.panX}, ${STATE.panY}) scale(${STATE.zoom})`
  );
}

let animFrame = null;
function startAnim() {
  let tick = 0;
  function frame() {
    if (STATE.dragging || tick < 60) {
      simulate(0.85);
      render();
    }
    tick++;
    animFrame = requestAnimationFrame(frame);
  }
  animFrame = requestAnimationFrame(frame);
}

// ============ HIGHLIGHTING ============
function hoverNode(id) {
  if (STATE.dragging) return;
  STATE.highlightId = id;
  const neighbours = id ? STATE.edgeSet.get(id) || new Set() : null;

  document.querySelectorAll('.mm-node').forEach(g => {
    const nid = +g.dataset.id;
    if (id === null) { g.classList.remove('hi','dim'); return; }
    if (nid === id) g.classList.add('hi');
    else g.classList.remove('hi');
    if (nid === id || neighbours.has(nid)) g.classList.remove('dim');
    else g.classList.add('dim');
  });
  document.querySelectorAll('.mm-edge').forEach(line => {
    const a = +line.dataset.a, b = +line.dataset.b;
    if (id === null) { line.classList.remove('hi','dim'); return; }
    if (a === id || b === id) { line.classList.add('hi'); line.classList.remove('dim'); }
    else { line.classList.remove('hi'); line.classList.add('dim'); }
  });
}

// ============ DRAG + PAN + ZOOM ============
const svg = document.getElementById('mm-svg');

function clientToScene(ev) {
  const pt = svg.createSVGPoint();
  pt.x = ev.clientX; pt.y = ev.clientY;
  const inv = svg.getScreenCTM().inverse();
  const svgP = pt.matrixTransform(inv);
  return {
    x: (svgP.x - STATE.panX) / STATE.zoom,
    y: (svgP.y - STATE.panY) / STATE.zoom
  };
}

function startDrag(ev, node, el) {
  ev.preventDefault();
  ev.stopPropagation();
  STATE.dragging = { node, el, startX: node.x, startY: node.y, moved: false };
  el.setPointerCapture(ev.pointerId);
  node.fixed = true;
  svg.classList.add('dragging');
}

svg.addEventListener('pointermove', ev => {
  if (STATE.dragging) {
    const p = clientToScene(ev);
    const n = STATE.dragging.node;
    if (Math.hypot(p.x - STATE.dragging.startX, p.y - STATE.dragging.startY) > 3) {
      STATE.dragging.moved = true;
    }
    n.x = p.x; n.y = p.y; n.vx = 0; n.vy = 0;
    render();
  } else if (STATE.panStart) {
    STATE.panX = STATE.panStart.panX + (ev.clientX - STATE.panStart.x);
    STATE.panY = STATE.panStart.panY + (ev.clientY - STATE.panStart.y);
    render();
  }
});

svg.addEventListener('pointerup', ev => {
  if (STATE.dragging) {
    const { node, el, moved } = STATE.dragging;
    node.fixed = false;
    svg.classList.remove('dragging');
    el.releasePointerCapture?.(ev.pointerId);
    STATE.dragging = null;
    if (moved) ev.stopPropagation();
  }
  STATE.panStart = null;
});

svg.addEventListener('pointerdown', ev => {
  if (ev.target.closest('.mm-node')) return;
  STATE.panStart = { x: ev.clientX, y: ev.clientY, panX: STATE.panX, panY: STATE.panY };
  svg.classList.add('dragging');
  closePopover();
});

svg.addEventListener('pointerup', () => svg.classList.remove('dragging'));
svg.addEventListener('pointerleave', () => { STATE.panStart = null; svg.classList.remove('dragging'); });

svg.addEventListener('wheel', ev => {
  ev.preventDefault();
  const rect = svg.getBoundingClientRect();
  const mx = ev.clientX - rect.left;
  const my = ev.clientY - rect.top;
  const factor = ev.deltaY < 0 ? 1.15 : 1/1.15;
  const newZoom = Math.max(0.25, Math.min(3, STATE.zoom * factor));
  // Zoom um Mauszeiger
  STATE.panX = mx - (mx - STATE.panX) * (newZoom / STATE.zoom);
  STATE.panY = my - (my - STATE.panY) * (newZoom / STATE.zoom);
  STATE.zoom = newZoom;
  render();
}, { passive: false });

document.getElementById('mm-zoom-in').addEventListener('click', () => zoomBy(1.25));
document.getElementById('mm-zoom-out').addEventListener('click', () => zoomBy(0.8));
document.getElementById('mm-zoom-fit').addEventListener('click', fitToView);

function zoomBy(factor) {
  const cx = STATE.width/2, cy = STATE.height/2;
  const newZoom = Math.max(0.25, Math.min(3, STATE.zoom * factor));
  STATE.panX = cx - (cx - STATE.panX) * (newZoom / STATE.zoom);
  STATE.panY = cy - (cy - STATE.panY) * (newZoom / STATE.zoom);
  STATE.zoom = newZoom;
  render();
}

function fitToView() {
  if (!STATE.nodes.length) return;
  let xs = STATE.nodes.map(n => n.x), ys = STATE.nodes.map(n => n.y);
  const minX = Math.min(...xs), maxX = Math.max(...xs);
  const minY = Math.min(...ys), maxY = Math.max(...ys);
  const bx = maxX - minX + 200, by = maxY - minY + 200;
  const z = Math.min(STATE.width / bx, STATE.height / by, 1.4);
  STATE.zoom = z;
  STATE.panX = STATE.width/2  - ((minX + maxX)/2) * z;
  STATE.panY = STATE.height/2 - ((minY + maxY)/2) * z;
  render();
}

// ============ POPOVER ============
const pop = document.getElementById('mm-pop');
function openPopover(n, nodeEl) {
  if (STATE.dragging && STATE.dragging.moved) return;
  const rect = svg.getBoundingClientRect();
  const cx = rect.left + STATE.panX + n.x * STATE.zoom;
  const cy = rect.top  + STATE.panY + n.y * STATE.zoom;

  const body = document.getElementById('mm-pop-body');
  const modHtml = (n.modules || []).map(m =>
    `<span class="mm-pop-mod" style="color:${modColor(m)}">${modIcon(m)} ${esc(modLabel(m))}</span>`
  ).join('');
  const cat = n.category ? (CATEGORY_LABEL[n.category] || n.category) : '';

  // Verwandte: direkte Nachbarn laut edgeSet
  const neighbours = STATE.edgeSet.get(n.id) || new Set();
  const relHtml = [...neighbours].map(id => {
    const r = STATE.byId.get(id);
    return r ? `<button data-id="${r.id}">${esc(r.title)}</button>` : '';
  }).join('');

  body.innerHTML = `
    ${cat ? `<span class="mm-pop-cat">${esc(cat)}</span>` : ''}
    <div class="mm-pop-title">${esc(n.title)}</div>
    ${n.short ? `<div class="mm-pop-short">${esc(n.short)}</div>` : ''}
    ${modHtml ? `<div class="mm-pop-mods">${modHtml}</div>` : ''}
    <div class="mm-pop-actions">
      <a class="mm-pop-btn primary" href="${PAGES}/glossar.php?term=${encodeURIComponent(n.key_slug)}" target="_blank" rel="noopener">
        📖 Im Glossar öffnen
        <small>Vollständiger Eintrag: Text, Beispiele, Links, Grafik</small>
      </a>
      <button class="mm-pop-btn" id="mm-pop-center">
        🎯 Knoten zentrieren
        <small>Zoomt zu diesem Begriff und hebt Verbindungen hervor</small>
      </button>
    </div>
    ${relHtml ? `<div class="mm-pop-related">
      <div class="mm-pop-related-title">Verbunden mit</div>
      <div class="mm-pop-related-list">${relHtml}</div>
    </div>` : ''}
  `;

  // Positionieren: rechts neben Node wenn Platz, sonst links
  pop.classList.add('open');
  const pw = pop.offsetWidth;
  const ph = pop.offsetHeight;
  let px = cx + 30, py = cy - ph/2;
  if (px + pw > window.innerWidth - 10) px = cx - pw - 30;
  if (py < rect.top + 10) py = rect.top + 10;
  if (py + ph > window.innerHeight - 10) py = window.innerHeight - ph - 10;
  pop.style.left = px + 'px';
  pop.style.top  = py + 'px';

  hoverNode(n.id);

  document.getElementById('mm-pop-center').addEventListener('click', () => {
    STATE.zoom = Math.max(STATE.zoom, 1.2);
    STATE.panX = STATE.width/2  - n.x * STATE.zoom;
    STATE.panY = STATE.height/2 - n.y * STATE.zoom;
    render();
    closePopover();
  });
  pop.querySelectorAll('.mm-pop-related-list button').forEach(btn => {
    btn.addEventListener('click', () => {
      const next = STATE.byId.get(+btn.dataset.id);
      if (next) openPopover(next, document.querySelector(`.mm-node[data-id="${next.id}"]`));
    });
  });
}
function closePopover() {
  pop.classList.remove('open');
  hoverNode(null);
}
document.getElementById('mm-pop-close').addEventListener('click', closePopover);
document.addEventListener('keydown', ev => { if (ev.key === 'Escape') closePopover(); });

// ============ SUCHE ============
document.getElementById('mm-search').addEventListener('input', ev => {
  const q = ev.target.value.trim().toLowerCase();
  if (!q) {
    document.querySelectorAll('.mm-node').forEach(g => g.classList.remove('hi','dim'));
    document.querySelectorAll('.mm-edge').forEach(l => l.classList.remove('hi','dim'));
    return;
  }
  const matches = new Set();
  STATE.nodes.forEach(n => {
    const hay = (n.title + ' ' + (n.short||'') + ' ' + n.key_slug).toLowerCase();
    if (hay.includes(q)) matches.add(n.id);
  });
  document.querySelectorAll('.mm-node').forEach(g => {
    const id = +g.dataset.id;
    if (matches.has(id)) { g.classList.add('hi'); g.classList.remove('dim'); }
    else { g.classList.remove('hi'); g.classList.add('dim'); }
  });
  document.querySelectorAll('.mm-edge').forEach(l => {
    const a = +l.dataset.a, b = +l.dataset.b;
    if (matches.has(a) && matches.has(b)) { l.classList.add('hi'); l.classList.remove('dim'); }
    else { l.classList.add('dim'); l.classList.remove('hi'); }
  });
});

// ============ RESIZE ============
function resize() {
  const vp = document.getElementById('mm-viewport');
  STATE.width = vp.clientWidth;
  STATE.height = vp.clientHeight;
  svg.setAttribute('viewBox', `0 0 ${STATE.width} ${STATE.height}`);
}
window.addEventListener('resize', () => { resize(); render(); });

load();
</script>
</body>
</html>
