8f89613eee
- ggs-quick-intro.js: optionale zweite Seite (config.page2 {title, html})
mit Weiter/Zurueck-Navigation. Rueckwaertskompatibel (ohne page2 = 1 Seite).
- Busfahrt: EN-Button entfernt; die "Warum Englisch?"-Erklaerung liegt jetzt
auf Onboarding-Seite 2 + Satz zur mehrsprachigen Hintergrundmusik.
- Onboarding-Schritte ohne lange "-" ausformuliert.
- Auftrags-Card (.bf-order) 20px tiefer (top 84).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
185 lines
8.2 KiB
JavaScript
185 lines
8.2 KiB
JavaScript
/**
|
||
* 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 { '<': '<', '>': '>', '&': '&', '"': '"' }[c];
|
||
});
|
||
}
|
||
|
||
var _config = null;
|
||
var _back = null;
|
||
var _page = 1;
|
||
|
||
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 onBackClick(e) {
|
||
if (e.target === _back) { markSeen(); close(); return; }
|
||
var a = e.target && e.target.dataset && e.target.dataset.action;
|
||
if (a === 'next') { _page = 2; render(); }
|
||
else if (a === 'back') { _page = 1; render(); }
|
||
else if (a === 'start') { markSeen(); close(); }
|
||
}
|
||
|
||
function render() {
|
||
injectCss();
|
||
var p2 = _config.page2 || null;
|
||
var hasP2 = !!(p2 && (p2.html || p2.body));
|
||
|
||
var headTitle, headLead, body, foot;
|
||
if (_page === 2 && hasP2) {
|
||
headTitle = p2.title || _config.title || 'Mehr dazu';
|
||
headLead = '';
|
||
body = (p2.html || p2.body || '');
|
||
foot = '<button type="button" class="ggs-qi-btn" data-action="back" style="background:#e8e4d8;color:#2a2a2a">← Zurück</button>'
|
||
+ '<button type="button" class="ggs-qi-btn ggs-qi-btn-primary" data-action="start">Los geht\'s →</button>';
|
||
} else {
|
||
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('');
|
||
headTitle = _config.title || 'Anleitung';
|
||
headLead = _config.lead ? '<p class="ggs-qi-lead">' + escapeHtml(_config.lead) + '</p>' : '';
|
||
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>' : '');
|
||
foot = hasP2
|
||
? '<button type="button" class="ggs-qi-btn ggs-qi-btn-primary" data-action="next">Weiter →</button>'
|
||
: '<button type="button" class="ggs-qi-btn ggs-qi-btn-primary" data-action="start">Los geht\'s →</button>';
|
||
}
|
||
|
||
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(headTitle) + '</h2>'
|
||
+ headLead
|
||
+ '</div>'
|
||
+ '<div class="ggs-qi-body">' + body + '</div>'
|
||
+ '<div class="ggs-qi-foot">' + foot + '</div>'
|
||
+ '</div>';
|
||
|
||
if (!_back) {
|
||
_back = document.createElement('div');
|
||
_back.className = 'ggs-qi-back';
|
||
_back.addEventListener('click', onBackClick);
|
||
document.body.appendChild(_back);
|
||
document.addEventListener('keydown', onKey);
|
||
}
|
||
_back.innerHTML = html;
|
||
var m = _back.querySelector('.ggs-qi-modal'); if (m) m.scrollTop = 0;
|
||
}
|
||
|
||
function open() {
|
||
if (!_config) return;
|
||
close();
|
||
_page = 1;
|
||
render();
|
||
}
|
||
|
||
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,
|
||
};
|
||
})();
|