/* ============================================================ GGS-HEADER — Einheitliche Sim-Kopfzeile (Entwurf B) ------------------------------------------------------------ Überführt das vorhandene
-Markup (Design-System) in die schwebende Pillen-Optik OHNE die Spiel-Logik anzufassen: bestehende IDs/Klassen (#btn-sound, #btn-reset, [data-speed], #level-badge, #ggs-music …) bleiben erhalten, die Handler der Sim greifen weiter. Nutzung: einfach ggs-header.css + ggs-header.js einbinden. Auto-Init auf DOMContentLoaded für jedes .ggs-header. Optionen über data-Attribute am
: data-ggs-home Home-Ziel (Default: vorh. Home-Link / "/schueler") data-ggs-glossar Glossar-URL (Default: "/glossar") data-ggs-sim Sim-ID (für /glossar?module=) data-ggs-no-glossar Glossar-Button weglassen ============================================================ */ (function () { 'use strict'; var SPRITE_ID = 'ggs-header-sprite'; var ICONS = { home: '', tempo: '', music: '', sound: '', 'sound-off': '', restart: '', help: '', glossar: '', pause: '', caret: '' }; /* SVG-Sprite einmalig ins Dokument legen */ function injectSprite() { if (document.getElementById(SPRITE_ID)) return; var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute('id', SPRITE_ID); svg.setAttribute('width', '0'); svg.setAttribute('height', '0'); svg.setAttribute('aria-hidden', 'true'); svg.style.position = 'absolute'; var defs = ''; for (var key in ICONS) { var stroked = (key === 'pause'); defs += '' + ICONS[key] + ''; } svg.innerHTML = '' + defs + ''; document.body.insertBefore(svg, document.body.firstChild); } function icon(name, cls) { return ''; } function setIcon(el, name, title) { if (!el) return; el.innerHTML = icon(name); if (title) el.setAttribute('title', title); } /* Ersten passenden Button per ID-Liste finden (bespoke IDs abdecken) */ function byIds(scope, ids) { for (var i = 0; i < ids.length; i++) { var el = scope.querySelector('#' + ids[i]); if (el) return el; } return null; } var SOUND_IDS = ['btn-sound', 'btn-mute', 'btnVoiceToggle', 'btnAudioToggle', 'btnAudio', 'audio-toggle', 'btnSound', 'btn-ton', 'btn-audio']; var RESET_IDS = ['btn-reset', 'btnReset', 'btn-restart', 'btn-neustart', 'reset-defaults-toggle']; var HELP_IDS = ['btn-info', 'btnHelp', 'btn-help', 'btn-anleitung', 'btn-hilfe', 'help-toggle']; var GLOSSAR_IDS = ['glossar-btn', 'btn-glossar', 'btnGlossar', 'btnGlossary']; var MUSIC_IDS = ['btnMusic', 'btnMusik', 'music-toggle-btn', 'bgm-toggle']; // sim-eigener Musik-Toggle var HOME_IDS = ['btnHome', 'btn-home', 'ggs-home-link', 'cockpit-link']; /* Glyph-Buttons (Ton), die die Sim per textContent umschaltet: Icon nach jeder Änderung nachziehen, Mute-Zustand als Icon spiegeln. */ function watchGlyphBtn(btn, mapFn, title) { if (!btn) return; if (title) btn.setAttribute('title', title); var apply = function () { if (btn.querySelector('svg.ic')) return; // schon Icon → nichts tun setIcon(btn, mapFn(btn.textContent || '')); }; apply(); new MutationObserver(apply).observe(btn, { childList: true, characterData: true, subtree: true }); } /* Standard-Musikplayer (Klima-Tracks als Plattform-Default) für Sims ohne eigenen Player. Aktiviert per data-ggs-music="default". */ // Plattform-Standardmusik (Platzhalter: Klima-Tracks, dort abgelegt) var DEFAULT_TRACKS = [ { label: '🎹 Lounge', file: '/sims/klima/assets/music/piano-lobby.mp3' }, { label: '🌿 Ambient', file: '/sims/klima/assets/music/fingertip-rain.mp3' }, { label: '🌙 Ruhig', file: '/sims/klima/assets/music/limestone-lullaby.mp3' }, { label: '☕ Chill', file: '/sims/klima/assets/music/vibrocoffee-calm.mp3' }, { label: '🎭 Drama', file: '/sims/klima/assets/music/rising-pressure.mp3' } ]; function buildDefaultMusic() { var wrap = document.createElement('div'); wrap.className = 'ggs-music'; wrap.id = 'ggs-music'; var sel = document.createElement('select'); sel.className = 'ggs-music-sel'; sel.id = 'ggs-music-sel'; sel.title = 'Musik wählen'; DEFAULT_TRACKS.forEach(function (t) { var o = document.createElement('option'); o.value = t.file; o.textContent = t.label; sel.appendChild(o); }); var btn = document.createElement('button'); btn.className = 'ggs-music-btn'; btn.id = 'ggs-music-toggle'; btn.type = 'button'; btn.title = 'Play / Pause'; btn.textContent = '▶'; var vol = document.createElement('input'); vol.type = 'range'; vol.className = 'ggs-music-vol'; vol.id = 'ggs-music-vol'; vol.min = 0; vol.max = 100; vol.value = 30; vol.title = 'Lautstärke'; wrap.appendChild(sel); wrap.appendChild(btn); wrap.appendChild(vol); var audio = new Audio(); audio.loop = true; audio.volume = 0.3; audio.src = DEFAULT_TRACKS[0].file; var playing = false; function play() { audio.play().then(function () { playing = true; btn.textContent = '⏸'; btn.classList.add('playing'); }).catch(function () {}); } function pause() { audio.pause(); playing = false; btn.textContent = '▶'; btn.classList.remove('playing'); } btn.addEventListener('click', function (e) { e.stopPropagation(); playing ? pause() : play(); }); sel.addEventListener('change', function () { audio.src = sel.value; if (playing) play(); }); vol.addEventListener('input', function () { audio.volume = (vol.value || 0) / 100; }); return wrap; } /* ------------------------------------------------------------ */ function enhance(header, opts) { if (!header || header.hasAttribute('data-ggs-enhanced')) return; opts = opts || {}; injectSprite(); header.setAttribute('data-ggs-enhanced', '1'); header.classList.add('ggs-header--float'); if (opts.overlay || header.getAttribute('data-ggs-overlay') === '1') header.classList.add('ggs-header--overlay'); var q = function (sel) { return header.querySelector(sel); }; var homeHref = opts.home || header.getAttribute('data-ggs-home') || '/schueler'; var simId = opts.sim || header.getAttribute('data-ggs-sim') || ''; var glossarHref = opts.glossar || header.getAttribute('data-ggs-glossar') || ('/glossar' + (simId ? '?module=' + encodeURIComponent(simId) : '')); var actions = q('.ggs-header-actions'); /* --- 1) Marken-Pille: Logo · Titel · Level ---------------- */ var logo = q('.ggs-header-logo'); var pille = document.createElement('div'); pille.className = 'ggs-hpille'; if (logo) { logo.parentNode.insertBefore(pille, logo); } else { header.insertBefore(pille, header.firstChild); // Fallback: Kreis-Logo erzeugen, falls die Sim keins im Header hat logo = document.createElement('a'); logo.className = 'ggs-header-logo'; logo.setAttribute('href', homeHref); var limg = document.createElement('img'); limg.className = 'ggs-header-logo-icon'; limg.src = opts.logo || header.getAttribute('data-ggs-logo') || '/assets/img/bildLogo.png'; limg.alt = ''; logo.appendChild(limg); } pille.appendChild(logo); var mod = q('.ggs-header-module'); if (mod) pille.appendChild(mod); var badges = header.querySelectorAll('.ggs-header-badge'); for (var i = 0; i < badges.length; i++) pille.appendChild(badges[i]); var tick = header.querySelector('.kw-tick-dot'); if (tick) pille.appendChild(tick); /* --- 2) Home-Button links neben die Pille ----------------- */ var homeBtn = byIds(header, HOME_IDS); if (!homeBtn) { var acts = header.querySelectorAll('a, button'); for (var h = 0; h < acts.length; h++) { var a = acts[h]; var href = a.getAttribute('href') || ''; var t = a.textContent || ''; if (a.id === 'btn-info' || a.id === 'btn-sound' || a.id === 'btn-reset' || a.id === 'ggs-lehrplan-link') continue; if (/schueler|cockpit|\/$/.test(href) || t.indexOf('🏠') >= 0 || t.indexOf('⌂') >= 0) { homeBtn = a; break; } } } if (!homeBtn) { homeBtn = document.createElement('a'); if (actions) actions.appendChild(homeBtn); } homeBtn.className = 'ggs-btn-ghost'; if (!homeBtn.getAttribute('href')) homeBtn.setAttribute('href', homeHref); setIcon(homeBtn, 'home', 'Startseite'); header.insertBefore(homeBtn, pille.nextSibling); /* Spacer sicherstellen (zwischen Home-Gruppe und Aktionen) */ var spacer = q('.ggs-header-spacer'); if (!spacer) { spacer = document.createElement('div'); spacer.className = 'ggs-header-spacer'; } header.insertBefore(spacer, actions); /* --- 3) Aktions-Icons setzen (bespoke IDs via Listen) ----- */ var helpBtn = byIds(header, HELP_IDS); var soundBtn = byIds(header, SOUND_IDS); var resetBtn = byIds(header, RESET_IDS); setIcon(helpBtn, 'help', 'Hilfe / Anleitung'); watchGlyphBtn(soundBtn, function (t) { return /🔇|🔈|🔕|off|aus|mute|stumm/i.test(t) ? 'sound-off' : 'sound'; }, 'Ton an/aus'); setIcon(resetBtn, 'restart', 'Neu starten'); setIcon(byIds(header, GLOSSAR_IDS), 'glossar'); // sim-interner Glossar/Fachinfo-Button setIcon(byIds(header, MUSIC_IDS), 'music'); // sim-eigener Musik-Toggle (öffnet eigenes Panel) var lehrplan = q('#ggs-lehrplan-link'); if (lehrplan) lehrplan.style.display = 'none'; /* --- 4) Tempo-Panel (nur wenn .ggs-speed vorhanden) ------- */ var speed = q('.ggs-speed'); var tempoWrap = null, spdBtn = null; if (speed) { tempoWrap = document.createElement('div'); tempoWrap.className = 'ggs-tempo'; spdBtn = document.createElement('button'); spdBtn.className = 'ggs-spd'; spdBtn.type = 'button'; spdBtn.setAttribute('title', 'Tempo'); var pop = document.createElement('div'); pop.className = 'ggs-pop ggs-pop--tempo'; speed.parentNode.insertBefore(tempoWrap, speed); pop.appendChild(speed); tempoWrap.appendChild(pop); tempoWrap.appendChild(spdBtn); var syncSpeed = function () { var active = speed.querySelector('.ggs-speed-btn.active') || speed.querySelector('.ggs-speed-btn[data-speed="1"]'); if (!active) return; var sp = active.getAttribute('data-speed'); // klassische Speed-Buttons var txt = (active.textContent || '').replace(/\s+/g, ' ').trim(); var isAuto = /auto/i.test(active.getAttribute('title') || '') || active.hasAttribute('data-auto') || active.getAttribute('data-mode') === 'auto'; var isPause = sp === '0' || active.getAttribute('data-mode') === 'pause' || /^\s*(pause|⏸)/i.test(txt); if (isPause) { spdBtn.innerHTML = icon('pause'); } else if (isAuto) { // Auto-Label (z. B. „Auto 1×") steht schon voll im Text → nicht doppeln spdBtn.innerHTML = /auto/i.test(txt) ? '' + txt + '' : '' + (txt || 'Auto') + 'Auto'; } else { spdBtn.innerHTML = '' + (txt || (sp + '×')) + ''; } spdBtn.classList.toggle('act', isPause || isAuto || (sp !== null && sp !== '1')); }; syncSpeed(); // Klick auf ein Segment: Sim-Handler läuft weiter, Label spiegeln, Panel bleibt offen var spBtns = speed.querySelectorAll('.ggs-speed-btn'); for (var s = 0; s < spBtns.length; s++) { spBtns[s].addEventListener('click', function () { setTimeout(syncSpeed, 0); }); } // Ein Observer über den ganzen Speed-Block: fängt Klassen-Flip UND Text-Änderung (Auto 1×↔8×) var mo = new MutationObserver(syncSpeed); mo.observe(speed, { subtree: true, childList: true, characterData: true, attributes: true, attributeFilter: ['class'] }); spdBtn.addEventListener('click', function (e) { e.stopPropagation(); tempoWrap.classList.toggle('open'); }); } /* --- 5) Musik-Panel ---------------------------------------- */ // Default-Player erzeugen, falls die Sim keinen eigenen Player hat var musicSpec = opts.music || header.getAttribute('data-ggs-music'); if (musicSpec && !q('#ggs-music') && actions) { actions.appendChild(buildDefaultMusic()); } var music = q('#ggs-music'); var musicWrap = null; if (music) { musicWrap = document.createElement('div'); musicWrap.className = 'ggs-music-wrap'; var mtrigger = document.createElement('button'); mtrigger.className = 'ggs-mtrigger'; mtrigger.type = 'button'; mtrigger.setAttribute('title', 'Musik'); mtrigger.innerHTML = icon('music'); var mpop = document.createElement('div'); mpop.className = 'ggs-pop ggs-pop--music'; music.parentNode.insertBefore(musicWrap, music); music.removeAttribute('hidden'); mpop.appendChild(music); musicWrap.appendChild(mtrigger); musicWrap.appendChild(mpop); mtrigger.addEventListener('click', function (e) { e.stopPropagation(); musicWrap.classList.toggle('open'); }); // Nach Song-Auswahl Panel schließen var sel = music.querySelector('#ggs-music-sel, select'); if (sel) sel.addEventListener('change', function () { musicWrap.classList.remove('open'); }); } /* --- 6) Glossar-Button (target=_blank) -------------------- */ if (!opts.noGlossar && !header.hasAttribute('data-ggs-no-glossar') && actions) { var gl = document.createElement('a'); gl.className = 'ggs-btn-ghost ggs-glossar'; gl.setAttribute('href', glossarHref); gl.setAttribute('target', '_blank'); gl.setAttribute('rel', 'noopener'); setIcon(gl, 'glossar', 'Glossar (neuer Tab)'); actions.appendChild(gl); } /* --- 7) Aktionen in Mockup-Reihenfolge sortieren ---------- */ if (actions) { // Standard-Steuerung ans Ende (Musik · Ton · Neustart · Hilfe · Glossar) var tail = [musicWrap, soundBtn, resetBtn, helpBtn, q('.ggs-glossar')]; for (var o = 0; o < tail.length; o++) if (tail[o]) actions.appendChild(tail[o]); // Tempo IMMER ganz links (erste Position der Aktionen) if (tempoWrap) actions.insertBefore(tempoWrap, actions.firstChild); } /* --- 8) Panels bei Außen-Klick schließen ------------------ */ document.addEventListener('click', function (e) { if (musicWrap && !musicWrap.contains(e.target)) musicWrap.classList.remove('open'); // Tempo bleibt bewusst offen (Toggle) — nur Klick auf den Button schließt }); return header; } function autoInit() { var headers = document.querySelectorAll('.ggs-header:not([data-ggs-enhanced])'); for (var i = 0; i < headers.length; i++) { if (headers[i].getAttribute('data-ggs-no-float') === '1') continue; try { enhance(headers[i]); } catch (err) { if (window.console) console.warn('[ggs-header] enhance fehlgeschlagen:', err); } } } window.GGSHeader = { enhance: enhance, autoInit: autoInit, icon: icon }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', autoInit); } else { autoInit(); } })();