Files
geograsim/App/assets/js/ggs-quick-intro.js
T
Adminator 1e51ef7def Nachtrag: alle bisher untracked Ordner + hängende Änderungen mit-committen
- Konzept/, didaktik_geografie/, didaktik_simulation/, v2-modules/, v2-platform/
- 12 code-workspace-Files
- STATUS-*.md
- viele M/D/R-Änderungen an bereits getrackten Files
- .gitignore verstärkt: **/.humaninput/, **/secret_keys.txt

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-08 02:27:02 +02:00

157 lines
7.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* GeoGraSim Plattform-Standard: Mini-Onboarding
* ==============================================
* Für Sims, die KEIN volles Tutorial haben aber Lernenden trotzdem einen
* kurzen Einstieg geben sollen: ein einfaches Modal mit Headline, Lead,
* 2-4 Bullet-Schritten und optionalen Lernzielen.
*
* USAGE in der Sim:
* <script src="/assets/js/ggs-quick-intro.js" defer></script>
* <script>
* window.addEventListener('DOMContentLoaded', () => {
* GgsQuickIntro.init({
* simId: 'busfahrt',
* title: '🚌 Busfahrt',
* lead: 'Du übernimmst Aufträge und transportierst Passagiere durch Europa.',
* steps: [
* 'Auftrag wählen (links) — du siehst Stadt, Distanz und Bezahlung.',
* 'Auf der Karte das richtige Ziel anklicken — Genauigkeit zählt.',
* 'Quiz beantworten — Hauptstadt + Land — sammelt Bonus.',
* 'Budget hochhalten, Aufträge gewinnen, Stufen freischalten.',
* ],
* goals: [
* 'Europäische Hauptstädte und Länder zuordnen',
* 'Distanzen einschätzen',
* 'Wirtschaftlich planen (Treibstoff, Marge)',
* ],
* });
* });
* </script>
*
* VERHALTEN:
* - Beim 1. Besuch (kein localStorage-Key) öffnet sich das Modal automatisch.
* - Schließen → localStorage merkt sich „gesehen" (key: ggs.intro.<simId>).
* - Registriert automatisch `window.GGS_TUTORIAL.replay()` als Plattform-API,
* damit der globale -Button im Header das Modal erneut öffnen kann.
* - „Anleitung erneut öffnen" verfügbar via `GgsQuickIntro.open()` oder
* `window.GGS_TUTORIAL.replay()`.
*
* STYLE: CSS wird automatisch injiziert. Mobile-first, iPad-tauglich,
* Esc + Tap-outside schließt.
*
* KOMPATIBILITÄT: Pure Vanilla-JS. Funktioniert mit existierendem
* Glossar-Tooltip-Pattern und GGS_TUTORIAL-Convention.
*/
(function () {
'use strict';
var CSS = ''
+ '.ggs-qi-back{position:fixed;inset:0;background:rgba(20,30,30,.55);display:flex;align-items:center;justify-content:center;z-index:9998;padding:2vw;animation:ggs-qi-fade .2s ease-out}'
+ '.ggs-qi-modal{background:#fff;border-radius:14px;max-width:520px;width:100%;max-height:90vh;overflow-y:auto;box-shadow:0 18px 40px rgba(0,0,0,.28);font-family:inherit;animation:ggs-qi-pop .22s ease-out;-webkit-overflow-scrolling:touch}'
+ '.ggs-qi-head{padding:18px 22px 8px;border-bottom:1px solid rgba(0,0,0,.05)}'
+ '.ggs-qi-title{font-size:1.35rem;font-weight:800;color:#1f4b37;margin:0 0 .3rem}'
+ '.ggs-qi-lead{font-size:.96rem;color:#3a3a3a;margin:0;line-height:1.45}'
+ '.ggs-qi-body{padding:14px 22px 4px;font-size:.94rem;line-height:1.5;color:#2a2a2a}'
+ '.ggs-qi-h3{font-size:.78rem;text-transform:uppercase;letter-spacing:.04em;color:#5a5a5a;margin:14px 0 6px;font-weight:700}'
+ '.ggs-qi-steps{margin:0;padding:0 0 0 1.2rem;list-style:decimal}'
+ '.ggs-qi-steps li{margin:.35rem 0}'
+ '.ggs-qi-goals{margin:0;padding:0;list-style:none}'
+ '.ggs-qi-goals li{padding:.28rem 0 .28rem 1.4rem;position:relative}'
+ '.ggs-qi-goals li::before{content:"🎯";position:absolute;left:0;font-size:.92em}'
+ '.ggs-qi-foot{display:flex;justify-content:flex-end;gap:.6rem;padding:14px 22px 18px;border-top:1px solid rgba(0,0,0,.05);background:#fafaf6;border-radius:0 0 14px 14px;position:sticky;bottom:0}'
+ '.ggs-qi-btn{font-family:inherit;font-size:.94rem;font-weight:700;padding:.55rem 1.1rem;border-radius:8px;border:0;cursor:pointer;transition:transform .08s,background .15s}'
+ '.ggs-qi-btn-primary{background:#1f4b37;color:#fff}'
+ '.ggs-qi-btn-primary:hover{background:#173527}'
+ '.ggs-qi-btn-primary:active{transform:scale(.97)}'
+ '@keyframes ggs-qi-fade{from{opacity:0}to{opacity:1}}'
+ '@keyframes ggs-qi-pop{from{opacity:0;transform:translateY(12px) scale(.97)}to{opacity:1;transform:none}}'
+ '@media (max-width:520px){.ggs-qi-modal{max-height:96vh}.ggs-qi-title{font-size:1.18rem}.ggs-qi-foot{padding-bottom:max(18px,env(safe-area-inset-bottom))}}';
var CSS_INJECTED = false;
function injectCss() {
if (CSS_INJECTED) return;
var s = document.createElement('style');
s.id = 'ggs-quick-intro-css';
s.textContent = CSS;
document.head.appendChild(s);
CSS_INJECTED = true;
}
function escapeHtml(s) {
return String(s == null ? '' : s).replace(/[<>&"]/g, function (c) {
return { '<': '&lt;', '>': '&gt;', '&': '&amp;', '"': '&quot;' }[c];
});
}
var _config = null;
var _back = null;
function lsKey() { return _config && _config.simId ? 'ggs.intro.' + _config.simId : null; }
function markSeen() { try { var k = lsKey(); if (k) localStorage.setItem(k, '1'); } catch (_) {} }
function isSeen() { try { var k = lsKey(); return k && localStorage.getItem(k) === '1'; } catch (_) { return false; } }
function close() {
if (!_back) return;
_back.remove();
_back = null;
document.removeEventListener('keydown', onKey);
}
function onKey(e) { if (e.key === 'Escape') close(); }
function open() {
if (!_config) return;
injectCss();
close();
var steps = (_config.steps || []).map(function (s) {
return '<li>' + (s.indexOf('<') >= 0 ? s : escapeHtml(s)) + '</li>';
}).join('');
var goals = (_config.goals || []).map(function (g) {
return '<li>' + escapeHtml(g) + '</li>';
}).join('');
var html = ''
+ '<div class="ggs-qi-modal" role="dialog" aria-modal="true" aria-labelledby="ggs-qi-title">'
+ '<div class="ggs-qi-head">'
+ '<h2 id="ggs-qi-title" class="ggs-qi-title">' + escapeHtml(_config.title || 'Anleitung') + '</h2>'
+ (_config.lead ? '<p class="ggs-qi-lead">' + escapeHtml(_config.lead) + '</p>' : '')
+ '</div>'
+ '<div class="ggs-qi-body">'
+ (steps ? '<div class="ggs-qi-h3">Was tust du?</div><ol class="ggs-qi-steps">' + steps + '</ol>' : '')
+ (goals ? '<div class="ggs-qi-h3">Was lernst du?</div><ul class="ggs-qi-goals">' + goals + '</ul>' : '')
+ '</div>'
+ '<div class="ggs-qi-foot">'
+ '<button type="button" class="ggs-qi-btn ggs-qi-btn-primary" data-action="start">Los geht\'s →</button>'
+ '</div>'
+ '</div>';
_back = document.createElement('div');
_back.className = 'ggs-qi-back';
_back.innerHTML = html;
_back.addEventListener('click', function (e) {
if (e.target === _back) { markSeen(); close(); }
if (e.target && e.target.dataset && e.target.dataset.action === 'start') { markSeen(); close(); }
});
document.body.appendChild(_back);
document.addEventListener('keydown', onKey);
}
window.GgsQuickIntro = {
init: function (config) {
_config = config || {};
// Plattform-Convention: GGS_TUTORIAL.replay() wird vom -Button im Header gerufen.
window.GGS_TUTORIAL = {
simId: _config.simId,
title: _config.title,
replay: function () { open(); },
};
// Beim 1. Besuch automatisch öffnen.
if (!isSeen()) {
// Kleiner Delay, damit Sim-Boot zuerst sein DOM aufbauen kann.
setTimeout(open, 400);
}
},
open: open,
close: close,
};
})();