Textsystem: Bis-Strich mit Leerzeichen (Zahl – Zahl) per Agent-Sweep
562 Ersetzungen in 50 Dateien: Halbgeviertstrich zwischen zwei Ziffern (= "bis"-Bereich) bekommt Leerzeichen davor/danach (3–4 -> 3 – 4). Regex /([0-9])\s*–\s*(?=[0-9])/ auf Rohtext (format-erhaltend, CRLF/Einrueckung unveraendert), Bindestriche in Woertern und Minuszeichen unangetastet, alle JSON weiter gueltig. Energiemanager-Zeitbloecke sind Display-Labels (kein Logik-Key) -> safe. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@@ -0,0 +1,34 @@
|
|||||||
|
const { chromium } = require('@playwright/test');
|
||||||
|
(async () => {
|
||||||
|
const b = await chromium.launch(); const p = await b.newPage();
|
||||||
|
const errs=[]; p.on('pageerror',e=>errs.push(e.message)); p.on('console',m=>{if(m.type()==='error')errs.push(m.text());});
|
||||||
|
await p.route('**/api/admin', r => r.fulfill({ contentType:'application/json', body: JSON.stringify({authenticated:true}) }));
|
||||||
|
await p.goto('http://localhost/geograsim/App/admin-weltkueche.html');
|
||||||
|
await p.waitForTimeout(2000);
|
||||||
|
const r = await p.evaluate(() => {
|
||||||
|
const dishes = [...document.querySelectorAll('.dish')];
|
||||||
|
const names = dishes.map(d => d.querySelector('.nm')?.textContent);
|
||||||
|
function dishInfo(namePart){
|
||||||
|
const d = dishes.find(x => (x.querySelector('.nm')?.textContent||'').toLowerCase().includes(namePart));
|
||||||
|
if(!d) return null;
|
||||||
|
const rows = [...d.querySelectorAll('tbody tr')].map(tr => ({
|
||||||
|
ing: tr.querySelector('.ing-nm')?.textContent.replace(/\s+/g,' ').trim(),
|
||||||
|
ok: [...tr.querySelectorAll('.cty.ok')].map(c=>c.textContent.replace(/\s+/g,' ').trim()),
|
||||||
|
dist: [...tr.querySelectorAll('.cty.dist')].map(c=>c.title.split(' · ')[0]),
|
||||||
|
}));
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
const groestl = dishInfo('tiroler gröstl');
|
||||||
|
const kart = groestl && groestl.find(r => /Kartoffel/i.test(r.ing));
|
||||||
|
const eierRow = groestl && groestl.find(r => /Eier/i.test(r.ing));
|
||||||
|
return {
|
||||||
|
dishCount: dishes.length,
|
||||||
|
sampleNames: names.slice(0,6),
|
||||||
|
kartoffelOk: kart ? kart.ok : null,
|
||||||
|
eierOk: eierRow ? eierRow.ok : null,
|
||||||
|
warnCount: document.querySelectorAll('.warn').length,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
console.log(JSON.stringify({result:r, pageErrors:errs}, null, 2));
|
||||||
|
await b.close();
|
||||||
|
})();
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
const { chromium } = require('@playwright/test');
|
||||||
|
(async () => {
|
||||||
|
const browser = await chromium.launch();
|
||||||
|
const page = await browser.newPage();
|
||||||
|
const r = await page.request.get('https://geograsim.at/teacher.html');
|
||||||
|
const html = await r.text();
|
||||||
|
console.log('STATUS', r.status(), 'LEN', html.length);
|
||||||
|
console.log('has renderLiveGesamt:', html.includes('renderLiveGesamt'));
|
||||||
|
console.log('has _liveQualityBar:', html.includes('_liveQualityBar'));
|
||||||
|
console.log('has tab-bar:', html.includes('tab-bar'));
|
||||||
|
console.log('has Ergebnisse:', html.includes('Ergebnisse'));
|
||||||
|
console.log('has external script src (module?):', /<script[^>]+src=/i.test(html));
|
||||||
|
// show script tags
|
||||||
|
const scripts = [...html.matchAll(/<script[^>]*>/gi)].map(m=>m[0]);
|
||||||
|
console.log('SCRIPT TAGS:', JSON.stringify(scripts, null, 2));
|
||||||
|
// show first 600 chars to see redirect logic
|
||||||
|
console.log('HEAD:', html.slice(0, 1200));
|
||||||
|
await browser.close();
|
||||||
|
})();
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
const { chromium } = require('@playwright/test');
|
||||||
|
(async () => {
|
||||||
|
const browser = await chromium.launch();
|
||||||
|
const page = await browser.newPage();
|
||||||
|
const r = await page.request.get('https://geograsim.at/teacher.html');
|
||||||
|
const html = await r.text();
|
||||||
|
|
||||||
|
const idxLogin = html.indexOf('login.html');
|
||||||
|
console.log('--- context around first login.html reference ---');
|
||||||
|
console.log(html.slice(idxLogin - 400, idxLogin + 120));
|
||||||
|
|
||||||
|
for (const name of ['renderLiveGesamt', '_liveQualityBar']) {
|
||||||
|
const idx = html.indexOf('function ' + name);
|
||||||
|
console.log('\n--- "function ' + name + '" @' + idx + ' ---');
|
||||||
|
if (idx >= 0) console.log(html.slice(idx - 60, idx + 120));
|
||||||
|
}
|
||||||
|
|
||||||
|
const idxErg = html.indexOf('Ergebnisse');
|
||||||
|
console.log('\n--- context around Ergebnisse ---');
|
||||||
|
console.log(html.slice(idxErg - 220, idxErg + 80));
|
||||||
|
await browser.close();
|
||||||
|
})();
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
const { chromium } = require('@playwright/test');
|
||||||
|
(async () => {
|
||||||
|
const browser = await chromium.launch();
|
||||||
|
const page = await browser.newPage();
|
||||||
|
const r = await page.request.get('https://geograsim.at/teacher.html');
|
||||||
|
const html = await r.text();
|
||||||
|
|
||||||
|
// 1) Static tab-bar markup
|
||||||
|
const tb = html.indexOf('id="tab-bar"');
|
||||||
|
console.log('--- #tab-bar markup ---');
|
||||||
|
console.log(tb >= 0 ? html.slice(tb - 40, tb + 700) : 'tab-bar id NOT found');
|
||||||
|
|
||||||
|
// 2) Extract _liveQualityBar function body and run it in isolation
|
||||||
|
const start = html.indexOf('function _liveQualityBar');
|
||||||
|
// find matching braces
|
||||||
|
let i = html.indexOf('{', start), depth = 0, end = -1;
|
||||||
|
for (; i < html.length; i++) {
|
||||||
|
if (html[i] === '{') depth++;
|
||||||
|
else if (html[i] === '}') { depth--; if (depth === 0) { end = i + 1; break; } }
|
||||||
|
}
|
||||||
|
const fnSrc = html.slice(start, end);
|
||||||
|
const fn = new Function(fnSrc + '\nreturn _liveQualityBar;')();
|
||||||
|
const sample = fn(8, 2);
|
||||||
|
console.log('\n--- _liveQualityBar(8,2) OUTPUT ---');
|
||||||
|
console.log(sample);
|
||||||
|
console.log('\ncontains "80":', String(sample).includes('80'));
|
||||||
|
|
||||||
|
await browser.close();
|
||||||
|
})();
|
||||||
@@ -0,0 +1,294 @@
|
|||||||
|
/**
|
||||||
|
* gen-display-preview.js
|
||||||
|
* ----------------------
|
||||||
|
* Lokales Admin-Werkzeug: erstellt Bildschirmfotos aller Simulationen
|
||||||
|
* (Onboarding/Start + Spielverlauf) in zwei Auflösungen (iPad + Full HD)
|
||||||
|
* und generiert daraus eine statische Galerie-Seite.
|
||||||
|
*
|
||||||
|
* Ausgabe: App/.LocalDeveloperTools/display-preview/
|
||||||
|
* shots/<id>__<vp>__<screen>.png
|
||||||
|
* display_preview.html
|
||||||
|
*
|
||||||
|
* Start: node gen-display-preview.js
|
||||||
|
*/
|
||||||
|
const { chromium } = require('@playwright/test');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// --- Pfade ---------------------------------------------------------------
|
||||||
|
const APP_DIR = 'C:/xampp/htdocs/geograsim/App';
|
||||||
|
const SIMS_DIR = path.join(APP_DIR, 'sims');
|
||||||
|
const OUT_DIR = path.join(APP_DIR, '.LocalDeveloperTools', 'display-preview');
|
||||||
|
const SHOTS_DIR = path.join(OUT_DIR, 'shots');
|
||||||
|
const BASE_URL = 'http://localhost/geograsim/App/sims';
|
||||||
|
|
||||||
|
// --- Viewports -----------------------------------------------------------
|
||||||
|
const VIEWPORTS = [
|
||||||
|
{ key: 'ipad', label: 'iPad', w: 1180, h: 820 },
|
||||||
|
{ key: 'fullhd', label: 'Full HD', w: 1920, h: 1080 },
|
||||||
|
];
|
||||||
|
|
||||||
|
// --- Freundliche Namen ---------------------------------------------------
|
||||||
|
const NAMES = {
|
||||||
|
weltkueche: '🍲 Welt-Küche',
|
||||||
|
fluss: '🌊 Flussmanagement',
|
||||||
|
heli: '🚁 Helikopter-Navigation',
|
||||||
|
energiemanager: '⚡ Energiemanager',
|
||||||
|
'eu-werkstatt': '🇪🇺 EU-Werkstatt',
|
||||||
|
farmer: '🌾 Farmer',
|
||||||
|
busfahrt: '🚌 Busfahrt',
|
||||||
|
sonnensystem: '🪐 Sonnensystem',
|
||||||
|
tourismustal: '🏔️ Tourismustal',
|
||||||
|
tourismusregion: '🗺️ Tourismusregion',
|
||||||
|
logistik: '🚚 Logistik',
|
||||||
|
fluggesellschaft:'✈️ Fluggesellschaft',
|
||||||
|
staustufen: '💧 Staustufen',
|
||||||
|
entscheidungstag:'📅 Entscheidungstag',
|
||||||
|
vulkan: '🌋 Vulkan',
|
||||||
|
wal: '🐋 Wal',
|
||||||
|
kofferdetektiv: '🧳 Kofferdetektiv',
|
||||||
|
stadt: '🏙️ Stadt',
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Sims finden ---------------------------------------------------------
|
||||||
|
function findSims() {
|
||||||
|
return fs.readdirSync(SIMS_DIR, { withFileTypes: true })
|
||||||
|
.filter(d => d.isDirectory() && fs.existsSync(path.join(SIMS_DIR, d.name, 'game.html')))
|
||||||
|
.map(d => d.name)
|
||||||
|
.sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- In-Page: sichtbaren Start-/Weiter-Button klicken --------------------
|
||||||
|
async function clickStartLike(page) {
|
||||||
|
return await page.evaluate(() => {
|
||||||
|
const isVisible = (el) => {
|
||||||
|
if (!el) return false;
|
||||||
|
const r = el.getBoundingClientRect();
|
||||||
|
if (r.width < 4 || r.height < 4) return false;
|
||||||
|
if (r.bottom < 0 || r.right < 0) return false;
|
||||||
|
const st = getComputedStyle(el);
|
||||||
|
return st.visibility !== 'hidden' && st.display !== 'none' && st.opacity !== '0'
|
||||||
|
&& !el.disabled && !el.classList.contains('hidden');
|
||||||
|
};
|
||||||
|
// 1) Prioritäts-Selektoren (bekannte Start-/Primary-Buttons der Plattform)
|
||||||
|
const prio = [
|
||||||
|
'.ggs-qi-btn-primary', '[data-action="start"]',
|
||||||
|
'#btnStart', '#obs-start', '#phase-start',
|
||||||
|
'.sn-btn-primary', '.ggs-btn-primary',
|
||||||
|
'.era-card-cta', '.scene-btn',
|
||||||
|
'.map-btn', '.btn.primary', '.primary', '.next',
|
||||||
|
];
|
||||||
|
for (const sel of prio) {
|
||||||
|
const el = [...document.querySelectorAll(sel)].find(isVisible);
|
||||||
|
if (el) { el.click(); return sel + ' :: ' + (el.textContent || '').trim().slice(0, 24); }
|
||||||
|
}
|
||||||
|
// 2) Text-basiert — führende Emojis/Pfeile/Ziffern strippen, dann Anfang prüfen
|
||||||
|
const clean = (s) => (s || '').replace(/^[^\p{L}]+/u, '').trim();
|
||||||
|
const rx = /^(los geht|los!|los →|los\b|start|starten|jetzt starten|neue partie|partie starten|spielen|spiel starten|beginnen|anfangen|einsteigen|erntejahr starten|routenplanung starten|in dieser welt starten|szene|weiter|verstanden|fertig|karte wählen|auswählen)/i;
|
||||||
|
const cands = [...document.querySelectorAll('button, a.btn, a[role=button], .btn, [role=button], [onclick]')];
|
||||||
|
const el = cands.find(e => {
|
||||||
|
if (!isVisible(e)) return false;
|
||||||
|
const t = clean(e.textContent);
|
||||||
|
return t.length > 0 && t.length < 44 && rx.test(t);
|
||||||
|
});
|
||||||
|
if (el) { el.click(); return 'text :: ' + (el.textContent || '').trim().slice(0, 24); }
|
||||||
|
return null;
|
||||||
|
}).catch(() => null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Ein Screenshot ------------------------------------------------------
|
||||||
|
async function shoot(page, file) {
|
||||||
|
try {
|
||||||
|
await page.screenshot({ path: file }); // Viewport-Ausschnitt (exakte Auflösung)
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
console.log(' ! Screenshot-Fehler:', e.message.slice(0, 80));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const sims = findSims();
|
||||||
|
// Optional: nur bestimmte Sims neu schießen (HTML wird immer komplett neu erzeugt).
|
||||||
|
const onlyArg = (process.argv.find(a => a.startsWith('--only=')) || '').slice(7);
|
||||||
|
const shootList = onlyArg ? onlyArg.split(',').map(s => s.trim()).filter(Boolean) : sims;
|
||||||
|
console.log('Gefundene Sims (' + sims.length + '):', sims.join(', '));
|
||||||
|
if (onlyArg) console.log('Neu geschossen werden nur:', shootList.join(', '));
|
||||||
|
fs.mkdirSync(SHOTS_DIR, { recursive: true });
|
||||||
|
|
||||||
|
const browser = await chromium.launch();
|
||||||
|
// results[id][vp] = { start:bool, play:bool, play2:bool, errs:[] }
|
||||||
|
const results = {};
|
||||||
|
|
||||||
|
for (const vp of VIEWPORTS) {
|
||||||
|
console.log('\n===== Viewport ' + vp.label + ' (' + vp.w + '×' + vp.h + ') =====');
|
||||||
|
for (const id of shootList) {
|
||||||
|
results[id] = results[id] || {};
|
||||||
|
const r = { start: false, play: false, play2: false, errs: [] };
|
||||||
|
results[id][vp.key] = r;
|
||||||
|
|
||||||
|
const ctx = await browser.newContext({
|
||||||
|
viewport: { width: vp.w, height: vp.h },
|
||||||
|
deviceScaleFactor: 1,
|
||||||
|
});
|
||||||
|
const page = await ctx.newPage();
|
||||||
|
page.on('pageerror', e => { if (r.errs.length < 4) r.errs.push('PE:' + e.message.slice(0, 60)); });
|
||||||
|
page.on('console', m => { if (m.type() === 'error' && r.errs.length < 4) r.errs.push('CON:' + m.text().slice(0, 60)); });
|
||||||
|
|
||||||
|
const url = BASE_URL + '/' + id + '/game.html';
|
||||||
|
try {
|
||||||
|
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||||||
|
} catch (e) {
|
||||||
|
r.errs.push('GOTO:' + e.message.slice(0, 50));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1) Onboarding/Start
|
||||||
|
await page.waitForTimeout(2500);
|
||||||
|
r.start = await shoot(page, path.join(SHOTS_DIR, id + '__' + vp.key + '__start.png'));
|
||||||
|
|
||||||
|
// 2) Spielverlauf: mehrere Runden klicken (Onboarding schließen -> Menü -> Spiel -> Tutorial weg)
|
||||||
|
let clicks = 0, last = null;
|
||||||
|
for (let round = 0; round < 4; round++) {
|
||||||
|
const clicked = await clickStartLike(page);
|
||||||
|
if (!clicked) break;
|
||||||
|
clicks++;
|
||||||
|
if (clicked === last) break; // gleiches Element erneut -> Abbruch
|
||||||
|
last = clicked;
|
||||||
|
await page.waitForTimeout(800);
|
||||||
|
}
|
||||||
|
if (clicks > 0) {
|
||||||
|
await page.waitForTimeout(2500);
|
||||||
|
r.play = await shoot(page, path.join(SHOTS_DIR, id + '__' + vp.key + '__play.png'));
|
||||||
|
|
||||||
|
// 3) optionaler zweiter Play-Screen: noch ein Weiter/Start, falls vorhanden
|
||||||
|
const more = await clickStartLike(page);
|
||||||
|
if (more && more !== last) {
|
||||||
|
await page.waitForTimeout(2000);
|
||||||
|
r.play2 = await shoot(page, path.join(SHOTS_DIR, id + '__' + vp.key + '__play2.png'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const flags = [r.start ? 'S' : '-', r.play ? 'P' : '-', r.play2 ? 'P2' : '--'].join(' ');
|
||||||
|
console.log(' ' + id.padEnd(18) + '[' + flags + ']'
|
||||||
|
+ (clicks ? ' clicks=' + clicks : ' (kein Start-Button)')
|
||||||
|
+ (r.errs.length ? ' ' + r.errs.slice(0, 2).join(' | ') : ''));
|
||||||
|
|
||||||
|
await ctx.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await browser.close();
|
||||||
|
|
||||||
|
// --- HTML generieren ---------------------------------------------------
|
||||||
|
const genDate = new Date().toLocaleString('de-AT', {
|
||||||
|
year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit',
|
||||||
|
});
|
||||||
|
|
||||||
|
const screens = [
|
||||||
|
{ key: 'start', label: 'Onboarding / Start' },
|
||||||
|
{ key: 'play', label: 'Spielverlauf' },
|
||||||
|
{ key: 'play2', label: 'Spielverlauf (2)' },
|
||||||
|
];
|
||||||
|
|
||||||
|
function cell(id, vp, screenKey) {
|
||||||
|
const rel = 'shots/' + id + '__' + vp.key + '__' + screenKey + '.png';
|
||||||
|
const abs = path.join(SHOTS_DIR, id + '__' + vp.key + '__' + screenKey + '.png');
|
||||||
|
if (fs.existsSync(abs)) {
|
||||||
|
return '<a href="' + rel + '" target="_blank" class="shot">'
|
||||||
|
+ '<img loading="lazy" src="' + rel + '" alt="' + id + ' ' + vp.label + ' ' + screenKey + '">'
|
||||||
|
+ '<span class="cap">' + vp.label + ' · ' + vp.w + '×' + vp.h + '</span></a>';
|
||||||
|
}
|
||||||
|
return '<div class="missing">— kein Screenshot —<span class="cap">' + vp.label + '</span></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
let blocks = '';
|
||||||
|
for (const id of sims) {
|
||||||
|
const name = NAMES[id] || (id.charAt(0).toUpperCase() + id.slice(1));
|
||||||
|
// nur Screen-Zeilen zeigen, die mindestens ein Bild haben
|
||||||
|
const activeScreens = screens.filter(sc =>
|
||||||
|
VIEWPORTS.some(vp => fs.existsSync(path.join(SHOTS_DIR, id + '__' + vp.key + '__' + sc.key + '.png')))
|
||||||
|
);
|
||||||
|
const rows = (activeScreens.length ? activeScreens : [screens[0]]).map(sc =>
|
||||||
|
'<tr><th class="screen">' + sc.label + '</th>'
|
||||||
|
+ VIEWPORTS.map(vp => '<td>' + cell(id, vp, sc.key) + '</td>').join('')
|
||||||
|
+ '</tr>'
|
||||||
|
).join('\n');
|
||||||
|
|
||||||
|
blocks += '\n<section class="sim" id="sim-' + id + '">'
|
||||||
|
+ '<h2>' + name + ' <span class="simid">' + id + '</span></h2>'
|
||||||
|
+ '<table><thead><tr><th class="screen"></th>'
|
||||||
|
+ VIEWPORTS.map(vp => '<th>' + vp.label + ' <span class="res">' + vp.w + '×' + vp.h + '</span></th>').join('')
|
||||||
|
+ '</tr></thead><tbody>' + rows + '</tbody></table></section>';
|
||||||
|
}
|
||||||
|
|
||||||
|
const nav = sims.map(id =>
|
||||||
|
'<a href="#sim-' + id + '">' + (NAMES[id] || id) + '</a>'
|
||||||
|
).join('');
|
||||||
|
|
||||||
|
const html = `<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Display-Vorschau (lokal)</title>
|
||||||
|
<style>
|
||||||
|
:root { --bg:#f4f2ec; --card:#fff; --ink:#1f2b26; --accent:#1f4b37; --muted:#6b7a72; --line:rgba(0,0,0,.08); }
|
||||||
|
* { box-sizing:border-box; }
|
||||||
|
body { margin:0; font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif; background:var(--bg); color:var(--ink); }
|
||||||
|
header { background:var(--accent); color:#fff; padding:20px 28px; position:sticky; top:0; z-index:10; box-shadow:0 2px 10px rgba(0,0,0,.15); }
|
||||||
|
header h1 { margin:0; font-size:1.4rem; }
|
||||||
|
header .meta { font-size:.82rem; opacity:.85; margin-top:4px; }
|
||||||
|
nav.toc { display:flex; flex-wrap:wrap; gap:6px; padding:14px 28px; background:#e9e6dd; border-bottom:1px solid var(--line); }
|
||||||
|
nav.toc a { font-size:.82rem; text-decoration:none; color:var(--accent); background:#fff; border:1px solid var(--line); border-radius:999px; padding:4px 11px; }
|
||||||
|
nav.toc a:hover { background:var(--accent); color:#fff; }
|
||||||
|
main { padding:24px 28px 60px; max-width:1500px; margin:0 auto; }
|
||||||
|
section.sim { background:var(--card); border-radius:14px; padding:18px 20px 22px; margin-bottom:26px; box-shadow:0 4px 14px rgba(0,0,0,.06); scroll-margin-top:90px; }
|
||||||
|
section.sim h2 { margin:0 0 14px; font-size:1.25rem; color:var(--accent); display:flex; align-items:baseline; gap:10px; }
|
||||||
|
.simid { font-size:.72rem; font-weight:600; color:var(--muted); background:#eef0ec; padding:2px 8px; border-radius:6px; letter-spacing:.03em; }
|
||||||
|
table { width:100%; border-collapse:collapse; table-layout:fixed; }
|
||||||
|
thead th { text-align:left; font-size:.82rem; color:var(--muted); padding:0 0 8px; font-weight:700; }
|
||||||
|
thead th .res { font-weight:500; opacity:.7; }
|
||||||
|
th.screen { width:150px; vertical-align:top; font-size:.9rem; color:var(--ink); padding-top:8px; }
|
||||||
|
td { padding:6px 6px 14px; vertical-align:top; width:calc((100% - 150px)/2); }
|
||||||
|
a.shot { display:block; border:1px solid var(--line); border-radius:8px; overflow:hidden; background:#0d1512; text-decoration:none; }
|
||||||
|
a.shot img { display:block; width:100%; max-width:100%; height:auto; }
|
||||||
|
.cap { display:block; font-size:.72rem; color:var(--muted); padding:5px 8px; background:#fafafa; border-top:1px solid var(--line); }
|
||||||
|
a.shot .cap { color:#c8d4ce; background:#141f1a; border-top:none; }
|
||||||
|
.missing { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:6px; min-height:120px; border:1px dashed var(--line); border-radius:8px; color:var(--muted); font-size:.85rem; background:#faf9f5; }
|
||||||
|
.missing .cap { background:none; border:none; }
|
||||||
|
@media (max-width:820px){ th.screen{width:90px;} td{width:calc((100% - 90px)/2);} }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>Display-Vorschau (lokal)</h1>
|
||||||
|
<div class="meta">${sims.length} Simulationen · je Onboarding/Start + Spielverlauf · iPad 1180×820 & Full HD 1920×1080 · generiert am ${genDate}</div>
|
||||||
|
</header>
|
||||||
|
<nav class="toc">${nav}</nav>
|
||||||
|
<main>${blocks}
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
|
||||||
|
const outFile = path.join(OUT_DIR, 'display_preview.html');
|
||||||
|
fs.writeFileSync(outFile, html, 'utf8');
|
||||||
|
console.log('\nHTML geschrieben: ' + outFile);
|
||||||
|
|
||||||
|
// --- Zusammenfassung ---------------------------------------------------
|
||||||
|
console.log('\n===== ZUSAMMENFASSUNG =====');
|
||||||
|
let totalShots = 0, missingPlay = [];
|
||||||
|
for (const id of sims) {
|
||||||
|
const parts = [];
|
||||||
|
for (const vp of VIEWPORTS) {
|
||||||
|
const r = (results[id] && results[id][vp.key]) || {};
|
||||||
|
const n = (r.start ? 1 : 0) + (r.play ? 1 : 0) + (r.play2 ? 1 : 0);
|
||||||
|
totalShots += n;
|
||||||
|
parts.push(vp.label + ':' + n + '/3(' + (r.start ? 'S' : '-') + (r.play ? 'P' : '-') + (r.play2 ? '2' : '-') + ')');
|
||||||
|
if (!r.play) missingPlay.push(id + '/' + vp.key);
|
||||||
|
}
|
||||||
|
console.log(' ' + id.padEnd(18) + parts.join(' '));
|
||||||
|
}
|
||||||
|
console.log('\nScreenshots gesamt: ' + totalShots);
|
||||||
|
console.log('Fehlende Play-Screens: ' + (missingPlay.length ? missingPlay.join(', ') : 'keine'));
|
||||||
|
console.log('Seite: http://localhost/geograsim/App/.LocalDeveloperTools/display-preview/display_preview.html');
|
||||||
|
})();
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
const { chromium } = require('@playwright/test');
|
||||||
|
(async () => {
|
||||||
|
const browser = await chromium.launch();
|
||||||
|
const page = await browser.newPage();
|
||||||
|
const res = await page.goto('http://localhost/geograsim/App/modul-weltkueche');
|
||||||
|
await page.waitForTimeout(700);
|
||||||
|
const r = await page.evaluate(() => {
|
||||||
|
const sec = document.getElementById('lehrplan-bezug');
|
||||||
|
const komps = [...document.querySelectorAll('.md-kompetenz .md-kompetenz-title')].map(e=>e.textContent.trim());
|
||||||
|
const anchors = document.querySelectorAll('.md-anchor').length;
|
||||||
|
const countryChips = [...document.querySelectorAll('.md-lehrplan-filters .md-chip')].map(c=>c.textContent.trim());
|
||||||
|
const empty = document.body.textContent.includes('Noch keine Kompetenzen zugeordnet');
|
||||||
|
return { hasSection: !!sec, kompetenzTitles: komps, anchorCount: anchors, countryChips, showsEmpty: empty };
|
||||||
|
});
|
||||||
|
console.log(JSON.stringify({ httpStatus: res.status(), result: r }, null, 2));
|
||||||
|
await browser.close();
|
||||||
|
})();
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
const { chromium } = require('@playwright/test');
|
||||||
|
(async () => {
|
||||||
|
const browser = await chromium.launch();
|
||||||
|
const page = await browser.newPage();
|
||||||
|
const res = await page.goto('https://geograsim.at/modul-weltkueche', { waitUntil: 'networkidle' });
|
||||||
|
await page.waitForTimeout(700);
|
||||||
|
const r = await page.evaluate(() => {
|
||||||
|
const sec = document.getElementById('lehrplan-bezug');
|
||||||
|
const komps = [...document.querySelectorAll('.md-kompetenz .md-kompetenz-title')].map(e=>e.textContent.trim());
|
||||||
|
const anchors = document.querySelectorAll('.md-anchor').length;
|
||||||
|
const countryChips = [...document.querySelectorAll('.md-lehrplan-filters .md-chip')].map(c=>c.textContent.trim());
|
||||||
|
const empty = document.body.textContent.includes('Noch keine Kompetenzen zugeordnet');
|
||||||
|
return { hasSection: !!sec, kompetenzTitles: komps, anchorCount: anchors, countryChips, showsEmpty: empty };
|
||||||
|
});
|
||||||
|
console.log(JSON.stringify({ httpStatus: res.status(), result: r }, null, 2));
|
||||||
|
await browser.close();
|
||||||
|
})();
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
const { chromium } = require('@playwright/test');
|
||||||
|
(async () => {
|
||||||
|
const browser = await chromium.launch();
|
||||||
|
const page = await browser.newPage();
|
||||||
|
const errs=[]; page.on('pageerror',e=>errs.push(e.message));
|
||||||
|
await page.goto('http://localhost/geograsim/App/modul-weltkueche', { waitUntil:'domcontentloaded' });
|
||||||
|
await page.waitForTimeout(700);
|
||||||
|
|
||||||
|
async function snapshot() {
|
||||||
|
return await page.evaluate(() => {
|
||||||
|
const all = [...document.querySelectorAll('.md-anchor')];
|
||||||
|
const visible = all.filter(a => !a.classList.contains('hidden'));
|
||||||
|
const byType = t => visible.filter(a => a.dataset.anchorType===t).length;
|
||||||
|
const fachCountries = {};
|
||||||
|
visible.filter(a=>a.dataset.anchorType==='fach').forEach(a=>{ fachCountries[a.dataset.country]=(fachCountries[a.dataset.country]||0)+1; });
|
||||||
|
return { total: all.length, visible: visible.length, fachVisible: byType('fach'), fachCountries };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async function clickChip(country) {
|
||||||
|
await page.evaluate((c) => {
|
||||||
|
const btn = [...document.querySelectorAll('.md-chip')].find(b => (b.dataset.country||'')===c);
|
||||||
|
if (btn) btn.click();
|
||||||
|
}, country);
|
||||||
|
await page.waitForTimeout(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = await snapshot();
|
||||||
|
await clickChip(''); // Alle Länder
|
||||||
|
const alle = await snapshot();
|
||||||
|
await clickChip('DE'); // Deutschland
|
||||||
|
const de = await snapshot();
|
||||||
|
await clickChip('AT'); // Österreich
|
||||||
|
const at = await snapshot();
|
||||||
|
|
||||||
|
console.log(JSON.stringify({ start, alle, de, at, pageErrors: errs }, null, 2));
|
||||||
|
await browser.close();
|
||||||
|
})();
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
const { chromium } = require('@playwright/test');
|
||||||
|
(async () => {
|
||||||
|
const browser = await chromium.launch();
|
||||||
|
const page = await browser.newPage();
|
||||||
|
const errs=[]; page.on('pageerror',e=>errs.push(e.message));
|
||||||
|
await page.goto('https://geograsim.at/modul-weltkueche', { waitUntil:'domcontentloaded' });
|
||||||
|
await page.waitForTimeout(900);
|
||||||
|
async function snap(){ return await page.evaluate(()=>{ const all=[...document.querySelectorAll('.md-anchor')]; const vis=all.filter(a=>!a.classList.contains('hidden')); const fc={}; vis.filter(a=>a.dataset.anchorType==='fach').forEach(a=>fc[a.dataset.country]=(fc[a.dataset.country]||0)+1); return {total:all.length, visible:vis.length, fach:vis.filter(a=>a.dataset.anchorType==='fach').length, fachCountries:fc}; }); }
|
||||||
|
async function chip(c){ await page.evaluate(c=>{const b=[...document.querySelectorAll('.md-chip')].find(x=>(x.dataset.country||'')===c); if(b)b.click();},c); await page.waitForTimeout(200); }
|
||||||
|
await chip(''); const alle=await snap();
|
||||||
|
await chip('DE'); const de=await snap();
|
||||||
|
await chip('AT'); const at=await snap();
|
||||||
|
console.log(JSON.stringify({alle,de,at,pageErrors:errs},null,2));
|
||||||
|
await browser.close();
|
||||||
|
})();
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
const { chromium } = require('@playwright/test');
|
||||||
|
(async () => {
|
||||||
|
const b = await chromium.launch(); const p = await b.newPage();
|
||||||
|
await p.addInitScript(() => {
|
||||||
|
window.__GGS__ = { simId: 'test', baseUrl: '' };
|
||||||
|
window.fetch = () => Promise.resolve({ ok:true, json:()=>Promise.resolve({}) }); // Heartbeat stummschalten
|
||||||
|
});
|
||||||
|
await p.goto('about:blank');
|
||||||
|
await p.addScriptTag({ path: 'C:/xampp/htdocs/geograsim/App/assets/js/live-client.js' });
|
||||||
|
const read = () => p.evaluate(() => window.__GGS_LIVE__ ? window.__GGS_LIVE__.activeMs : null);
|
||||||
|
const set = (v) => p.evaluate((v) => window.__GGS_LIVE__.setPaused(v), v);
|
||||||
|
await p.waitForTimeout(300);
|
||||||
|
const a1 = await read();
|
||||||
|
await p.waitForTimeout(2500); const a2 = await read(); // sollte gestiegen sein
|
||||||
|
await set(true);
|
||||||
|
await p.waitForTimeout(2500); const a3 = await read(); // sollte eingefroren sein
|
||||||
|
await set(false);
|
||||||
|
await p.waitForTimeout(2500); const a4 = await read(); // sollte wieder steigen
|
||||||
|
const grewNormal = (a2 - a1) > 1500;
|
||||||
|
const frozen = (a3 - a2) < 300;
|
||||||
|
const resumed = (a4 - a3) > 1500;
|
||||||
|
console.log(JSON.stringify({ a1,a2,a3,a4, grewNormal, frozen, resumed,
|
||||||
|
verdict: (grewNormal && frozen && resumed) ? 'PASS' : 'FAIL' }, null, 2));
|
||||||
|
await b.close();
|
||||||
|
})();
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
const { chromium } = require('@playwright/test');
|
||||||
|
(async () => {
|
||||||
|
const browser = await chromium.launch();
|
||||||
|
const out = {};
|
||||||
|
|
||||||
|
// 1) modul-weltkueche: Lehrplan-Bezug jetzt vorhanden?
|
||||||
|
{
|
||||||
|
const page = await browser.newPage();
|
||||||
|
await page.goto('https://geograsim.at/modul-weltkueche', { waitUntil: 'domcontentloaded' });
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
out.modulWeltkueche = await page.evaluate(() => ({
|
||||||
|
kompetenzTitles: [...document.querySelectorAll('.md-kompetenz-title')].map(e=>e.textContent.trim()),
|
||||||
|
anchorCount: document.querySelectorAll('.md-anchor').length,
|
||||||
|
showsEmpty: document.body.textContent.includes('Noch keine Kompetenzen zugeordnet'),
|
||||||
|
}));
|
||||||
|
await page.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Weltküche-Sim: Artikel + Ukraine-Feedback
|
||||||
|
{
|
||||||
|
const page = await browser.newPage();
|
||||||
|
const errs=[]; page.on('pageerror',e=>errs.push(e.message));
|
||||||
|
await page.goto('https://geograsim.at/sims/weltkueche/game.html', { waitUntil:'domcontentloaded' });
|
||||||
|
await page.waitForTimeout(2000);
|
||||||
|
out.weltkueche = await page.evaluate(async () => {
|
||||||
|
try {
|
||||||
|
if (!DISHES.length) await loadWeltkuecheData();
|
||||||
|
const dish = DISHES.find(d=>d.id==='tiroler-groestl'); current={dish};
|
||||||
|
buildTaskFor('kartoffel');
|
||||||
|
return {
|
||||||
|
kartoffelArt: ING.kartoffel.art,
|
||||||
|
badArticles: Object.entries(ING).filter(([k,v])=>!['der','die','das'].includes(v&&v.art)).map(([k])=>k),
|
||||||
|
wrongUA: explainChoice('kartoffel','ukraine',false).text,
|
||||||
|
};
|
||||||
|
} catch(e){ return { error:String(e) }; }
|
||||||
|
});
|
||||||
|
out.weltkueche.pageErrors = errs;
|
||||||
|
await page.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) teacher.html: lädt + neue Funktionen da (direkter Link, den das Produkt nutzt)
|
||||||
|
{
|
||||||
|
const page = await browser.newPage();
|
||||||
|
const errs=[]; page.on('pageerror',e=>errs.push(e.message));
|
||||||
|
// teacher.html leitet ohne Login auf login.html um — Navigation abfangen, sofort auswerten
|
||||||
|
await page.route('**/login.html', r=>r.abort());
|
||||||
|
await page.goto('https://geograsim.at/teacher.html', { waitUntil:'domcontentloaded' }).catch(()=>{});
|
||||||
|
await page.waitForTimeout(1500);
|
||||||
|
out.teacher = await page.evaluate(() => ({
|
||||||
|
hasRenderGesamt: typeof renderLiveGesamt === 'function',
|
||||||
|
hasQualityBar: typeof _liveQualityBar === 'function',
|
||||||
|
qualitySample: (typeof _liveQualityBar==='function') ? _liveQualityBar(8,2) : null,
|
||||||
|
tabLabels: [...document.querySelectorAll('#tab-bar .tab')].map(t=>t.textContent.trim()),
|
||||||
|
})).catch(e=>({evalError:String(e)}));
|
||||||
|
out.teacher.pageErrors = errs;
|
||||||
|
await page.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4) Tourismusregion passstrasse Karte
|
||||||
|
{
|
||||||
|
const page = await browser.newPage();
|
||||||
|
const mc = await page.request.get('https://geograsim.at/sims/tourismusregion/js/maps-custom.json');
|
||||||
|
const j = await mc.json();
|
||||||
|
out.tourismus = {
|
||||||
|
keys: Object.keys(j),
|
||||||
|
passBergaufPoints: j.passstrasse?.routes?.bergauf?.length ?? null,
|
||||||
|
passSlots: (j.passstrasse?.slots||[]).length,
|
||||||
|
};
|
||||||
|
await page.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(JSON.stringify(out, null, 2));
|
||||||
|
await browser.close();
|
||||||
|
})();
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
const { chromium } = require('@playwright/test');
|
||||||
|
(async () => {
|
||||||
|
const b = await chromium.launch(); const p = await b.newPage();
|
||||||
|
const errs=[]; p.on('pageerror',e=>errs.push(e.message)); p.on('console',m=>{if(m.type()==='error')errs.push(m.text());});
|
||||||
|
await p.route('**/login.html', r=>r.abort());
|
||||||
|
await p.goto('http://localhost/geograsim/App/teacher.html',{waitUntil:'domcontentloaded'}).catch(()=>{});
|
||||||
|
await p.waitForTimeout(1200);
|
||||||
|
const r = await p.evaluate(() => ({
|
||||||
|
hasSimWord: typeof simWord==='function',
|
||||||
|
hasSimCode: typeof simCode==='function',
|
||||||
|
logistikWord: typeof simWord==='function' ? simWord('logistik') : null,
|
||||||
|
logistikCode: typeof simCode==='function' ? simCode('logistik') : null,
|
||||||
|
unknownCode: typeof simCode==='function' ? simCode('irgendwas') : null,
|
||||||
|
logistikFields: (typeof LIVE_PRIMARY_FIELDS==='object' && LIVE_PRIMARY_FIELDS.logistik) ? LIVE_PRIMARY_FIELDS.logistik.map(f=>f.label) : null,
|
||||||
|
liveShortLogistik: typeof LIVE_SIM_SHORT==='object' ? LIVE_SIM_SHORT.logistik : null,
|
||||||
|
// Kürzel-Eindeutigkeit
|
||||||
|
codesUnique: (function(){ if(typeof SIM_LABELS!=='object') return null; var seen={},dup=[]; Object.keys(SIM_LABELS).forEach(k=>{var c=SIM_LABELS[k].c; if(seen[c])dup.push(c); seen[c]=1;}); return dup; })(),
|
||||||
|
}));
|
||||||
|
console.log(JSON.stringify({result:r, pageErrors:errs}, null, 2));
|
||||||
|
await b.close();
|
||||||
|
})();
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
const { chromium } = require('@playwright/test');
|
||||||
|
(async () => {
|
||||||
|
const browser = await chromium.launch();
|
||||||
|
const page = await browser.newPage();
|
||||||
|
const errors = [];
|
||||||
|
page.on('pageerror', e => errors.push('PAGEERROR: '+e.message));
|
||||||
|
page.on('console', m => { if (m.type()==='error') errors.push(m.text()); });
|
||||||
|
const res = await page.goto('http://localhost/geograsim/App/teacher');
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
const r = await page.evaluate(() => {
|
||||||
|
const tabs = [...document.querySelectorAll('#tab-bar .tab')].map(t => t.textContent.trim());
|
||||||
|
let toggle = null;
|
||||||
|
try { toggle = (typeof _liveToggleHtml === 'function') ? _liveToggleHtml() : null; } catch(e){ toggle = 'ERR:'+e.message; }
|
||||||
|
return {
|
||||||
|
tabLabels: tabs,
|
||||||
|
hasSetLiveMode: typeof setLiveMode === 'function',
|
||||||
|
hasRenderGesamt: typeof renderLiveGesamt === 'function',
|
||||||
|
hasQualityBar: typeof _liveQualityBar === 'function',
|
||||||
|
qualitySample: (typeof _liveQualityBar === 'function') ? _liveQualityBar(8,2) : null,
|
||||||
|
toggleHasGesamt: (typeof toggle === 'string') ? toggle.includes('Gesamt') : toggle,
|
||||||
|
toggleHasTag: (typeof toggle === 'string') ? toggle.includes('Tag') : toggle,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
console.log(JSON.stringify({ httpStatus: res.status(), result: r, pageErrors: errors }, null, 2));
|
||||||
|
await browser.close();
|
||||||
|
})();
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
const { chromium } = require('@playwright/test');
|
||||||
|
(async () => {
|
||||||
|
const b = await chromium.launch(); const p = await b.newPage({ viewport:{width:1180,height:820} });
|
||||||
|
const errs=[]; p.on('pageerror',e=>errs.push(e.message)); p.on('console',m=>{if(m.type()==='error')errs.push(m.text());});
|
||||||
|
await p.goto('http://localhost/geograsim/App/sims/tourismusregion/game.html');
|
||||||
|
await p.waitForTimeout(1500);
|
||||||
|
await p.click('.map-btn'); // Route klicken → Spiel startet → Willkommenskarte
|
||||||
|
await p.waitForTimeout(2000);
|
||||||
|
const r = await p.evaluate(() => {
|
||||||
|
const card = document.getElementById('cardText');
|
||||||
|
const txt = card ? card.textContent : '';
|
||||||
|
return {
|
||||||
|
started: !!window.__ttState,
|
||||||
|
cardHasEnvText: /Umwelt ist deine Landschaft/.test(txt),
|
||||||
|
cardHasNaturschutz: /Naturschutzgebiet/.test(txt),
|
||||||
|
envKpi: document.getElementById('kEnv')?.textContent,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
console.log(JSON.stringify({ result:r, pageErrors: errs.filter(e=>!/404|Failed to load resource/.test(e)) }, null, 2));
|
||||||
|
await b.close();
|
||||||
|
})();
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
const { chromium } = require('@playwright/test');
|
||||||
|
(async () => {
|
||||||
|
const browser = await chromium.launch();
|
||||||
|
const page = await browser.newPage();
|
||||||
|
const errors = [];
|
||||||
|
page.on('pageerror', e => errors.push('PAGEERROR: '+e.message));
|
||||||
|
page.on('console', m => { if (m.type()==='error') errors.push(m.text()); });
|
||||||
|
|
||||||
|
// 1) maps-custom.json direkt prüfen
|
||||||
|
const mc = await page.request.get('http://localhost/geograsim/App/sims/tourismusregion/js/maps-custom.json');
|
||||||
|
const mcJson = await mc.json();
|
||||||
|
const ps = mcJson.passstrasse || null;
|
||||||
|
|
||||||
|
// 2) Hintergrundbild erreichbar?
|
||||||
|
const img = await page.request.get('http://localhost/geograsim/App/sims/tourismusregion/assets/maps/passstrasse.png');
|
||||||
|
|
||||||
|
// 3) Sim-Startscreen laden, Kartenliste prüfen
|
||||||
|
const res = await page.goto('http://localhost/geograsim/App/sims/tourismusregion/game.html');
|
||||||
|
await page.waitForTimeout(2500);
|
||||||
|
const startInfo = await page.evaluate(() => {
|
||||||
|
const list = document.getElementById('mapList');
|
||||||
|
const labels = list ? [...list.querySelectorAll('*')].map(e=>e.textContent).filter(t=>t && t.length<40) : [];
|
||||||
|
const hasPass = !!(list && /Passstra/i.test(list.textContent||''));
|
||||||
|
return { mapListText: (list?list.textContent:'').replace(/\s+/g,' ').trim().slice(0,300), hasPass };
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(JSON.stringify({
|
||||||
|
httpStatus: res.status(),
|
||||||
|
passInMapsCustom: !!ps,
|
||||||
|
routeKeys: ps ? Object.keys(ps.routes||{}) : null,
|
||||||
|
bergaufPoints: ps && ps.routes && ps.routes.bergauf ? ps.routes.bergauf.length : null,
|
||||||
|
slotCount: ps ? (ps.slots||[]).length : null,
|
||||||
|
bgImageStatus: img.status(),
|
||||||
|
startInfo,
|
||||||
|
pageErrors: errors
|
||||||
|
}, null, 2));
|
||||||
|
await browser.close();
|
||||||
|
})();
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
const { chromium } = require('@playwright/test');
|
||||||
|
(async () => {
|
||||||
|
const browser = await chromium.launch();
|
||||||
|
const page = await browser.newPage({ viewport: { width: 1180, height: 820 } }); // iPad Landscape
|
||||||
|
const errs=[]; page.on('pageerror',e=>errs.push(e.message)); page.on('console',m=>{if(m.type()==='error')errs.push(m.text());});
|
||||||
|
await page.goto('http://localhost/geograsim/App/sims/tourismusregion/game.html');
|
||||||
|
await page.waitForTimeout(1500);
|
||||||
|
|
||||||
|
const before = await page.evaluate(() => ({
|
||||||
|
hasPlayerName: !!document.getElementById('playerName'),
|
||||||
|
hasClassCode: !!document.getElementById('classCode'),
|
||||||
|
hasBtnStart: !!document.getElementById('btnStart'),
|
||||||
|
hasBtnSave: !!document.getElementById('btnSave'),
|
||||||
|
mapBtnCount: document.querySelectorAll('.map-btn').length,
|
||||||
|
startOverlayHidden: document.getElementById('startOverlay').classList.contains('hidden'),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// ② Klick auf erste Route sollte SOFORT starten
|
||||||
|
await page.click('.map-btn');
|
||||||
|
await page.waitForTimeout(2500);
|
||||||
|
const after = await page.evaluate(() => ({
|
||||||
|
startOverlayHidden: document.getElementById('startOverlay').classList.contains('hidden'),
|
||||||
|
gameStarted: !!window.__ttState,
|
||||||
|
mapId: window.__ttState && window.__ttState.map && window.__ttState.map.id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
await page.screenshot({ path: 'C:/Users/herr_/AppData/Local/Temp/claude/c--xampp-htdocs-geograsim/c02572ea-7616-4524-a974-dff0e5670d2c/scratchpad/tr-ipad.png' });
|
||||||
|
console.log(JSON.stringify({ before, after, pageErrors: errs }, null, 2));
|
||||||
|
await browser.close();
|
||||||
|
})();
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
const { chromium } = require('@playwright/test');
|
||||||
|
(async () => {
|
||||||
|
const browser = await chromium.launch();
|
||||||
|
const page = await browser.newPage();
|
||||||
|
const errors = [];
|
||||||
|
page.on('console', m => { if (m.type()==='error') errors.push(m.text()); });
|
||||||
|
page.on('pageerror', e => errors.push('PAGEERROR: '+e.message));
|
||||||
|
await page.goto('http://localhost/geograsim/App/sims/weltkueche/game.html');
|
||||||
|
await page.waitForTimeout(1500);
|
||||||
|
const r = await page.evaluate(async () => {
|
||||||
|
try {
|
||||||
|
if (!DISHES.length) await loadWeltkuecheData();
|
||||||
|
const dish = DISHES.find(d => d.id === 'tiroler-groestl');
|
||||||
|
if (!dish) return { error: 'no tiroler-groestl dish' };
|
||||||
|
current = { dish };
|
||||||
|
const task = buildTaskFor('kartoffel');
|
||||||
|
const art = ING.kartoffel.art;
|
||||||
|
const correctAT = explainChoice('kartoffel', 'oesterreich', true).text;
|
||||||
|
const correctDE = explainChoice('kartoffel', 'deutschland', true).text;
|
||||||
|
const wrongUA = explainChoice('kartoffel', 'ukraine', false).text;
|
||||||
|
const bad = Object.entries(ING).filter(([k,v]) => !['der','die','das'].includes(v && v.art)).map(([k])=>k);
|
||||||
|
return { art, taskCorrect: task.correct, correctAT, correctDE, wrongUA, badArticles: bad, ingCount: Object.keys(ING).length };
|
||||||
|
} catch (e) { return { error: String(e), stack: e.stack }; }
|
||||||
|
});
|
||||||
|
console.log(JSON.stringify({ result: r, consoleErrors: errors }, null, 2));
|
||||||
|
await browser.close();
|
||||||
|
})();
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
const { chromium } = require('@playwright/test');
|
||||||
|
(async () => {
|
||||||
|
const b = await chromium.launch(); const p = await b.newPage();
|
||||||
|
const errs=[]; p.on('pageerror',e=>errs.push(e.message)); p.on('console',m=>{if(m.type()==='error')errs.push(m.text());});
|
||||||
|
await p.goto('http://localhost/geograsim/App/sims/weltkueche/game.html');
|
||||||
|
await p.waitForTimeout(1500);
|
||||||
|
const r = await p.evaluate(async () => {
|
||||||
|
try {
|
||||||
|
if (!DISHES.length) await loadWeltkuecheData();
|
||||||
|
function taskFor(ingKey, heimat){
|
||||||
|
current = { dish: { id:'test-'+ingKey, heimat: heimat, ings:[ingKey], signature:{} } };
|
||||||
|
try { delete _taskCache[ingKey]; } catch(_){}
|
||||||
|
return buildTaskFor(ingKey);
|
||||||
|
}
|
||||||
|
const dEggsDE = _wkDist('oesterreich','deutschland');
|
||||||
|
const dEggsFR = _wkDist('oesterreich','frankreich');
|
||||||
|
const eier = taskFor('eier','oesterreich');
|
||||||
|
const kart = taskFor('kartoffel','oesterreich');
|
||||||
|
const milch = taskFor('milch','oesterreich');
|
||||||
|
// alle Zutaten müssen fehlerfrei bauen
|
||||||
|
let buildErr = null;
|
||||||
|
for (const k of Object.keys(ING)) {
|
||||||
|
current = { dish:{id:'x',heimat:'oesterreich',ings:[k],signature:{}} };
|
||||||
|
try { delete _taskCache[k]; buildTaskFor(k); } catch(e){ buildErr = k+': '+e.message; break; }
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
distDE: Math.round(dEggsDE), distFR: Math.round(dEggsFR),
|
||||||
|
eierCorrect: eier.correct, eierChoices: eier.choices,
|
||||||
|
eierDistractors: eier.choices.length - eier.correct.length,
|
||||||
|
kartoffelCorrect: kart.correct,
|
||||||
|
milchCorrect: milch.correct,
|
||||||
|
buildErr, ingCount: Object.keys(ING).length,
|
||||||
|
};
|
||||||
|
} catch(e){ return { error:String(e), stack:e.stack }; }
|
||||||
|
});
|
||||||
|
console.log(JSON.stringify({result:r, pageErrors:errs}, null, 2));
|
||||||
|
await b.close();
|
||||||
|
})();
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Display-Vorschau (lokal)</title>
|
||||||
|
<style>
|
||||||
|
:root { --bg:#f4f2ec; --card:#fff; --ink:#1f2b26; --accent:#1f4b37; --muted:#6b7a72; --line:rgba(0,0,0,.08); }
|
||||||
|
* { box-sizing:border-box; }
|
||||||
|
body { margin:0; font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif; background:var(--bg); color:var(--ink); }
|
||||||
|
header { background:var(--accent); color:#fff; padding:20px 28px; position:sticky; top:0; z-index:10; box-shadow:0 2px 10px rgba(0,0,0,.15); }
|
||||||
|
header h1 { margin:0; font-size:1.4rem; }
|
||||||
|
header .meta { font-size:.82rem; opacity:.85; margin-top:4px; }
|
||||||
|
nav.toc { display:flex; flex-wrap:wrap; gap:6px; padding:14px 28px; background:#e9e6dd; border-bottom:1px solid var(--line); }
|
||||||
|
nav.toc a { font-size:.82rem; text-decoration:none; color:var(--accent); background:#fff; border:1px solid var(--line); border-radius:999px; padding:4px 11px; }
|
||||||
|
nav.toc a:hover { background:var(--accent); color:#fff; }
|
||||||
|
main { padding:24px 28px 60px; max-width:1500px; margin:0 auto; }
|
||||||
|
section.sim { background:var(--card); border-radius:14px; padding:18px 20px 22px; margin-bottom:26px; box-shadow:0 4px 14px rgba(0,0,0,.06); scroll-margin-top:90px; }
|
||||||
|
section.sim h2 { margin:0 0 14px; font-size:1.25rem; color:var(--accent); display:flex; align-items:baseline; gap:10px; }
|
||||||
|
.simid { font-size:.72rem; font-weight:600; color:var(--muted); background:#eef0ec; padding:2px 8px; border-radius:6px; letter-spacing:.03em; }
|
||||||
|
table { width:100%; border-collapse:collapse; table-layout:fixed; }
|
||||||
|
thead th { text-align:left; font-size:.82rem; color:var(--muted); padding:0 0 8px; font-weight:700; }
|
||||||
|
thead th .res { font-weight:500; opacity:.7; }
|
||||||
|
th.screen { width:150px; vertical-align:top; font-size:.9rem; color:var(--ink); padding-top:8px; }
|
||||||
|
td { padding:6px 6px 14px; vertical-align:top; width:calc((100% - 150px)/2); }
|
||||||
|
a.shot { display:block; border:1px solid var(--line); border-radius:8px; overflow:hidden; background:#0d1512; text-decoration:none; }
|
||||||
|
a.shot img { display:block; width:100%; max-width:100%; height:auto; }
|
||||||
|
.cap { display:block; font-size:.72rem; color:var(--muted); padding:5px 8px; background:#fafafa; border-top:1px solid var(--line); }
|
||||||
|
a.shot .cap { color:#c8d4ce; background:#141f1a; border-top:none; }
|
||||||
|
.missing { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:6px; min-height:120px; border:1px dashed var(--line); border-radius:8px; color:var(--muted); font-size:.85rem; background:#faf9f5; }
|
||||||
|
.missing .cap { background:none; border:none; }
|
||||||
|
@media (max-width:820px){ th.screen{width:90px;} td{width:calc((100% - 90px)/2);} }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>Display-Vorschau (lokal)</h1>
|
||||||
|
<div class="meta">16 Simulationen · je Onboarding/Start + Spielverlauf · iPad 1180×820 & Full HD 1920×1080 · generiert am 19.08.2026, 15:06</div>
|
||||||
|
</header>
|
||||||
|
<nav class="toc"><a href="#sim-busfahrt">🚌 Busfahrt</a><a href="#sim-energiemanager">⚡ Energiemanager</a><a href="#sim-entscheidungstag">📅 Entscheidungstag</a><a href="#sim-eu-werkstatt">🇪🇺 EU-Werkstatt</a><a href="#sim-farmer">🌾 Farmer</a><a href="#sim-fluggesellschaft">✈️ Fluggesellschaft</a><a href="#sim-fluss">🌊 Flussmanagement</a><a href="#sim-heli">🚁 Helikopter-Navigation</a><a href="#sim-logistik">🚚 Logistik</a><a href="#sim-sonnensystem">🪐 Sonnensystem</a><a href="#sim-staustufen">💧 Staustufen</a><a href="#sim-tourismusregion">🗺️ Tourismusregion</a><a href="#sim-tourismustal">🏔️ Tourismustal</a><a href="#sim-vulkan">🌋 Vulkan</a><a href="#sim-wal">🐋 Wal</a><a href="#sim-weltkueche">🍲 Welt-Küche</a></nav>
|
||||||
|
<main>
|
||||||
|
<section class="sim" id="sim-busfahrt"><h2>🚌 Busfahrt <span class="simid">busfahrt</span></h2><table><thead><tr><th class="screen"></th><th>iPad <span class="res">1180×820</span></th><th>Full HD <span class="res">1920×1080</span></th></tr></thead><tbody><tr><th class="screen">Onboarding / Start</th><td><a href="shots/busfahrt__ipad__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/busfahrt__ipad__start.png" alt="busfahrt iPad start"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/busfahrt__fullhd__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/busfahrt__fullhd__start.png" alt="busfahrt Full HD start"><span class="cap">Full HD · 1920×1080</span></a></td></tr>
|
||||||
|
<tr><th class="screen">Spielverlauf</th><td><a href="shots/busfahrt__ipad__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/busfahrt__ipad__play.png" alt="busfahrt iPad play"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/busfahrt__fullhd__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/busfahrt__fullhd__play.png" alt="busfahrt Full HD play"><span class="cap">Full HD · 1920×1080</span></a></td></tr></tbody></table></section>
|
||||||
|
<section class="sim" id="sim-energiemanager"><h2>⚡ Energiemanager <span class="simid">energiemanager</span></h2><table><thead><tr><th class="screen"></th><th>iPad <span class="res">1180×820</span></th><th>Full HD <span class="res">1920×1080</span></th></tr></thead><tbody><tr><th class="screen">Onboarding / Start</th><td><a href="shots/energiemanager__ipad__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/energiemanager__ipad__start.png" alt="energiemanager iPad start"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/energiemanager__fullhd__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/energiemanager__fullhd__start.png" alt="energiemanager Full HD start"><span class="cap">Full HD · 1920×1080</span></a></td></tr>
|
||||||
|
<tr><th class="screen">Spielverlauf</th><td><a href="shots/energiemanager__ipad__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/energiemanager__ipad__play.png" alt="energiemanager iPad play"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/energiemanager__fullhd__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/energiemanager__fullhd__play.png" alt="energiemanager Full HD play"><span class="cap">Full HD · 1920×1080</span></a></td></tr>
|
||||||
|
<tr><th class="screen">Spielverlauf (2)</th><td><a href="shots/energiemanager__ipad__play2.png" target="_blank" class="shot"><img loading="lazy" src="shots/energiemanager__ipad__play2.png" alt="energiemanager iPad play2"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/energiemanager__fullhd__play2.png" target="_blank" class="shot"><img loading="lazy" src="shots/energiemanager__fullhd__play2.png" alt="energiemanager Full HD play2"><span class="cap">Full HD · 1920×1080</span></a></td></tr></tbody></table></section>
|
||||||
|
<section class="sim" id="sim-entscheidungstag"><h2>📅 Entscheidungstag <span class="simid">entscheidungstag</span></h2><table><thead><tr><th class="screen"></th><th>iPad <span class="res">1180×820</span></th><th>Full HD <span class="res">1920×1080</span></th></tr></thead><tbody><tr><th class="screen">Onboarding / Start</th><td><a href="shots/entscheidungstag__ipad__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/entscheidungstag__ipad__start.png" alt="entscheidungstag iPad start"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/entscheidungstag__fullhd__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/entscheidungstag__fullhd__start.png" alt="entscheidungstag Full HD start"><span class="cap">Full HD · 1920×1080</span></a></td></tr>
|
||||||
|
<tr><th class="screen">Spielverlauf</th><td><a href="shots/entscheidungstag__ipad__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/entscheidungstag__ipad__play.png" alt="entscheidungstag iPad play"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/entscheidungstag__fullhd__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/entscheidungstag__fullhd__play.png" alt="entscheidungstag Full HD play"><span class="cap">Full HD · 1920×1080</span></a></td></tr></tbody></table></section>
|
||||||
|
<section class="sim" id="sim-eu-werkstatt"><h2>🇪🇺 EU-Werkstatt <span class="simid">eu-werkstatt</span></h2><table><thead><tr><th class="screen"></th><th>iPad <span class="res">1180×820</span></th><th>Full HD <span class="res">1920×1080</span></th></tr></thead><tbody><tr><th class="screen">Onboarding / Start</th><td><a href="shots/eu-werkstatt__ipad__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/eu-werkstatt__ipad__start.png" alt="eu-werkstatt iPad start"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/eu-werkstatt__fullhd__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/eu-werkstatt__fullhd__start.png" alt="eu-werkstatt Full HD start"><span class="cap">Full HD · 1920×1080</span></a></td></tr>
|
||||||
|
<tr><th class="screen">Spielverlauf</th><td><a href="shots/eu-werkstatt__ipad__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/eu-werkstatt__ipad__play.png" alt="eu-werkstatt iPad play"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/eu-werkstatt__fullhd__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/eu-werkstatt__fullhd__play.png" alt="eu-werkstatt Full HD play"><span class="cap">Full HD · 1920×1080</span></a></td></tr></tbody></table></section>
|
||||||
|
<section class="sim" id="sim-farmer"><h2>🌾 Farmer <span class="simid">farmer</span></h2><table><thead><tr><th class="screen"></th><th>iPad <span class="res">1180×820</span></th><th>Full HD <span class="res">1920×1080</span></th></tr></thead><tbody><tr><th class="screen">Onboarding / Start</th><td><a href="shots/farmer__ipad__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/farmer__ipad__start.png" alt="farmer iPad start"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/farmer__fullhd__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/farmer__fullhd__start.png" alt="farmer Full HD start"><span class="cap">Full HD · 1920×1080</span></a></td></tr>
|
||||||
|
<tr><th class="screen">Spielverlauf</th><td><a href="shots/farmer__ipad__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/farmer__ipad__play.png" alt="farmer iPad play"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/farmer__fullhd__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/farmer__fullhd__play.png" alt="farmer Full HD play"><span class="cap">Full HD · 1920×1080</span></a></td></tr></tbody></table></section>
|
||||||
|
<section class="sim" id="sim-fluggesellschaft"><h2>✈️ Fluggesellschaft <span class="simid">fluggesellschaft</span></h2><table><thead><tr><th class="screen"></th><th>iPad <span class="res">1180×820</span></th><th>Full HD <span class="res">1920×1080</span></th></tr></thead><tbody><tr><th class="screen">Onboarding / Start</th><td><a href="shots/fluggesellschaft__ipad__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/fluggesellschaft__ipad__start.png" alt="fluggesellschaft iPad start"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/fluggesellschaft__fullhd__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/fluggesellschaft__fullhd__start.png" alt="fluggesellschaft Full HD start"><span class="cap">Full HD · 1920×1080</span></a></td></tr>
|
||||||
|
<tr><th class="screen">Spielverlauf</th><td><a href="shots/fluggesellschaft__ipad__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/fluggesellschaft__ipad__play.png" alt="fluggesellschaft iPad play"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/fluggesellschaft__fullhd__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/fluggesellschaft__fullhd__play.png" alt="fluggesellschaft Full HD play"><span class="cap">Full HD · 1920×1080</span></a></td></tr></tbody></table></section>
|
||||||
|
<section class="sim" id="sim-fluss"><h2>🌊 Flussmanagement <span class="simid">fluss</span></h2><table><thead><tr><th class="screen"></th><th>iPad <span class="res">1180×820</span></th><th>Full HD <span class="res">1920×1080</span></th></tr></thead><tbody><tr><th class="screen">Onboarding / Start</th><td><a href="shots/fluss__ipad__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/fluss__ipad__start.png" alt="fluss iPad start"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/fluss__fullhd__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/fluss__fullhd__start.png" alt="fluss Full HD start"><span class="cap">Full HD · 1920×1080</span></a></td></tr></tbody></table></section>
|
||||||
|
<section class="sim" id="sim-heli"><h2>🚁 Helikopter-Navigation <span class="simid">heli</span></h2><table><thead><tr><th class="screen"></th><th>iPad <span class="res">1180×820</span></th><th>Full HD <span class="res">1920×1080</span></th></tr></thead><tbody><tr><th class="screen">Onboarding / Start</th><td><a href="shots/heli__ipad__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/heli__ipad__start.png" alt="heli iPad start"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/heli__fullhd__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/heli__fullhd__start.png" alt="heli Full HD start"><span class="cap">Full HD · 1920×1080</span></a></td></tr>
|
||||||
|
<tr><th class="screen">Spielverlauf</th><td><a href="shots/heli__ipad__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/heli__ipad__play.png" alt="heli iPad play"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/heli__fullhd__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/heli__fullhd__play.png" alt="heli Full HD play"><span class="cap">Full HD · 1920×1080</span></a></td></tr></tbody></table></section>
|
||||||
|
<section class="sim" id="sim-logistik"><h2>🚚 Logistik <span class="simid">logistik</span></h2><table><thead><tr><th class="screen"></th><th>iPad <span class="res">1180×820</span></th><th>Full HD <span class="res">1920×1080</span></th></tr></thead><tbody><tr><th class="screen">Onboarding / Start</th><td><a href="shots/logistik__ipad__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/logistik__ipad__start.png" alt="logistik iPad start"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/logistik__fullhd__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/logistik__fullhd__start.png" alt="logistik Full HD start"><span class="cap">Full HD · 1920×1080</span></a></td></tr>
|
||||||
|
<tr><th class="screen">Spielverlauf</th><td><a href="shots/logistik__ipad__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/logistik__ipad__play.png" alt="logistik iPad play"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/logistik__fullhd__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/logistik__fullhd__play.png" alt="logistik Full HD play"><span class="cap">Full HD · 1920×1080</span></a></td></tr></tbody></table></section>
|
||||||
|
<section class="sim" id="sim-sonnensystem"><h2>🪐 Sonnensystem <span class="simid">sonnensystem</span></h2><table><thead><tr><th class="screen"></th><th>iPad <span class="res">1180×820</span></th><th>Full HD <span class="res">1920×1080</span></th></tr></thead><tbody><tr><th class="screen">Onboarding / Start</th><td><a href="shots/sonnensystem__ipad__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/sonnensystem__ipad__start.png" alt="sonnensystem iPad start"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/sonnensystem__fullhd__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/sonnensystem__fullhd__start.png" alt="sonnensystem Full HD start"><span class="cap">Full HD · 1920×1080</span></a></td></tr>
|
||||||
|
<tr><th class="screen">Spielverlauf</th><td><a href="shots/sonnensystem__ipad__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/sonnensystem__ipad__play.png" alt="sonnensystem iPad play"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/sonnensystem__fullhd__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/sonnensystem__fullhd__play.png" alt="sonnensystem Full HD play"><span class="cap">Full HD · 1920×1080</span></a></td></tr>
|
||||||
|
<tr><th class="screen">Spielverlauf (2)</th><td><a href="shots/sonnensystem__ipad__play2.png" target="_blank" class="shot"><img loading="lazy" src="shots/sonnensystem__ipad__play2.png" alt="sonnensystem iPad play2"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/sonnensystem__fullhd__play2.png" target="_blank" class="shot"><img loading="lazy" src="shots/sonnensystem__fullhd__play2.png" alt="sonnensystem Full HD play2"><span class="cap">Full HD · 1920×1080</span></a></td></tr></tbody></table></section>
|
||||||
|
<section class="sim" id="sim-staustufen"><h2>💧 Staustufen <span class="simid">staustufen</span></h2><table><thead><tr><th class="screen"></th><th>iPad <span class="res">1180×820</span></th><th>Full HD <span class="res">1920×1080</span></th></tr></thead><tbody><tr><th class="screen">Onboarding / Start</th><td><a href="shots/staustufen__ipad__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/staustufen__ipad__start.png" alt="staustufen iPad start"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/staustufen__fullhd__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/staustufen__fullhd__start.png" alt="staustufen Full HD start"><span class="cap">Full HD · 1920×1080</span></a></td></tr>
|
||||||
|
<tr><th class="screen">Spielverlauf</th><td><a href="shots/staustufen__ipad__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/staustufen__ipad__play.png" alt="staustufen iPad play"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/staustufen__fullhd__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/staustufen__fullhd__play.png" alt="staustufen Full HD play"><span class="cap">Full HD · 1920×1080</span></a></td></tr></tbody></table></section>
|
||||||
|
<section class="sim" id="sim-tourismusregion"><h2>🗺️ Tourismusregion <span class="simid">tourismusregion</span></h2><table><thead><tr><th class="screen"></th><th>iPad <span class="res">1180×820</span></th><th>Full HD <span class="res">1920×1080</span></th></tr></thead><tbody><tr><th class="screen">Onboarding / Start</th><td><a href="shots/tourismusregion__ipad__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/tourismusregion__ipad__start.png" alt="tourismusregion iPad start"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/tourismusregion__fullhd__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/tourismusregion__fullhd__start.png" alt="tourismusregion Full HD start"><span class="cap">Full HD · 1920×1080</span></a></td></tr>
|
||||||
|
<tr><th class="screen">Spielverlauf</th><td><a href="shots/tourismusregion__ipad__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/tourismusregion__ipad__play.png" alt="tourismusregion iPad play"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/tourismusregion__fullhd__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/tourismusregion__fullhd__play.png" alt="tourismusregion Full HD play"><span class="cap">Full HD · 1920×1080</span></a></td></tr></tbody></table></section>
|
||||||
|
<section class="sim" id="sim-tourismustal"><h2>🏔️ Tourismustal <span class="simid">tourismustal</span></h2><table><thead><tr><th class="screen"></th><th>iPad <span class="res">1180×820</span></th><th>Full HD <span class="res">1920×1080</span></th></tr></thead><tbody><tr><th class="screen">Onboarding / Start</th><td><a href="shots/tourismustal__ipad__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/tourismustal__ipad__start.png" alt="tourismustal iPad start"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/tourismustal__fullhd__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/tourismustal__fullhd__start.png" alt="tourismustal Full HD start"><span class="cap">Full HD · 1920×1080</span></a></td></tr>
|
||||||
|
<tr><th class="screen">Spielverlauf</th><td><a href="shots/tourismustal__ipad__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/tourismustal__ipad__play.png" alt="tourismustal iPad play"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/tourismustal__fullhd__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/tourismustal__fullhd__play.png" alt="tourismustal Full HD play"><span class="cap">Full HD · 1920×1080</span></a></td></tr></tbody></table></section>
|
||||||
|
<section class="sim" id="sim-vulkan"><h2>🌋 Vulkan <span class="simid">vulkan</span></h2><table><thead><tr><th class="screen"></th><th>iPad <span class="res">1180×820</span></th><th>Full HD <span class="res">1920×1080</span></th></tr></thead><tbody><tr><th class="screen">Onboarding / Start</th><td><a href="shots/vulkan__ipad__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/vulkan__ipad__start.png" alt="vulkan iPad start"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/vulkan__fullhd__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/vulkan__fullhd__start.png" alt="vulkan Full HD start"><span class="cap">Full HD · 1920×1080</span></a></td></tr>
|
||||||
|
<tr><th class="screen">Spielverlauf</th><td><a href="shots/vulkan__ipad__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/vulkan__ipad__play.png" alt="vulkan iPad play"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/vulkan__fullhd__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/vulkan__fullhd__play.png" alt="vulkan Full HD play"><span class="cap">Full HD · 1920×1080</span></a></td></tr>
|
||||||
|
<tr><th class="screen">Spielverlauf (2)</th><td><a href="shots/vulkan__ipad__play2.png" target="_blank" class="shot"><img loading="lazy" src="shots/vulkan__ipad__play2.png" alt="vulkan iPad play2"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/vulkan__fullhd__play2.png" target="_blank" class="shot"><img loading="lazy" src="shots/vulkan__fullhd__play2.png" alt="vulkan Full HD play2"><span class="cap">Full HD · 1920×1080</span></a></td></tr></tbody></table></section>
|
||||||
|
<section class="sim" id="sim-wal"><h2>🐋 Wal <span class="simid">wal</span></h2><table><thead><tr><th class="screen"></th><th>iPad <span class="res">1180×820</span></th><th>Full HD <span class="res">1920×1080</span></th></tr></thead><tbody><tr><th class="screen">Onboarding / Start</th><td><a href="shots/wal__ipad__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/wal__ipad__start.png" alt="wal iPad start"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/wal__fullhd__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/wal__fullhd__start.png" alt="wal Full HD start"><span class="cap">Full HD · 1920×1080</span></a></td></tr>
|
||||||
|
<tr><th class="screen">Spielverlauf</th><td><a href="shots/wal__ipad__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/wal__ipad__play.png" alt="wal iPad play"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/wal__fullhd__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/wal__fullhd__play.png" alt="wal Full HD play"><span class="cap">Full HD · 1920×1080</span></a></td></tr></tbody></table></section>
|
||||||
|
<section class="sim" id="sim-weltkueche"><h2>🍲 Welt-Küche <span class="simid">weltkueche</span></h2><table><thead><tr><th class="screen"></th><th>iPad <span class="res">1180×820</span></th><th>Full HD <span class="res">1920×1080</span></th></tr></thead><tbody><tr><th class="screen">Onboarding / Start</th><td><a href="shots/weltkueche__ipad__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/weltkueche__ipad__start.png" alt="weltkueche iPad start"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/weltkueche__fullhd__start.png" target="_blank" class="shot"><img loading="lazy" src="shots/weltkueche__fullhd__start.png" alt="weltkueche Full HD start"><span class="cap">Full HD · 1920×1080</span></a></td></tr>
|
||||||
|
<tr><th class="screen">Spielverlauf</th><td><a href="shots/weltkueche__ipad__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/weltkueche__ipad__play.png" alt="weltkueche iPad play"><span class="cap">iPad · 1180×820</span></a></td><td><a href="shots/weltkueche__fullhd__play.png" target="_blank" class="shot"><img loading="lazy" src="shots/weltkueche__fullhd__play.png" alt="weltkueche Full HD play"><span class="cap">Full HD · 1920×1080</span></a></td></tr></tbody></table></section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,282 @@
|
|||||||
|
/**
|
||||||
|
* regen-gameplay-shots.cjs
|
||||||
|
* ------------------------
|
||||||
|
* Ersetzt die "Spielverlauf"-Screenshots (play / play2) der Display-Vorschau
|
||||||
|
* durch ECHTE Spielszenen: Auswahl-Screens (Welt/Karte/Szenario) werden
|
||||||
|
* betreten und das Onboarding wird zuverlässig weggeklickt (Skip-Buttons +
|
||||||
|
* mehrseitige "Weiter"-Tutorials, auch wenn der Button unter dem Fold liegt),
|
||||||
|
* bevor der Screenshot fällt.
|
||||||
|
*
|
||||||
|
* Ersetzt NUR die play/play2-Bilder in-place; die start-Bilder (Onboarding)
|
||||||
|
* und display_preview.html bleiben unangetastet, damit die Galerie
|
||||||
|
* unverändert funktioniert.
|
||||||
|
*
|
||||||
|
* Start (aus dem playwright-Ordner wegen node_modules):
|
||||||
|
* cd /c/xampp/htdocs/geograsim/.dontDeploy/playwright && \
|
||||||
|
* NODE_PATH="C:\\xampp\\htdocs\\geograsim\\.dontDeploy\\playwright\\node_modules" \
|
||||||
|
* node ../../App/.LocalDeveloperTools/display-preview/regen-gameplay-shots.cjs
|
||||||
|
*
|
||||||
|
* Optional: --only=sonnensystem,wal --vp=ipad
|
||||||
|
*/
|
||||||
|
const { chromium } = require('@playwright/test');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const SHOTS_DIR = 'C:/xampp/htdocs/geograsim/App/.LocalDeveloperTools/display-preview/shots';
|
||||||
|
const BASE_URL = 'http://localhost/geograsim/App/sims';
|
||||||
|
|
||||||
|
const VIEWPORTS = [
|
||||||
|
{ key: 'ipad', w: 1180, h: 820 },
|
||||||
|
{ key: 'fullhd', w: 1920, h: 1080 },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Sims mit "play"-Screenshot in der Galerie (fluss hat keinen play -> weggelassen).
|
||||||
|
const SIMS = [
|
||||||
|
{ id: 'busfahrt' },
|
||||||
|
{ id: 'energiemanager', play2: true },
|
||||||
|
{ id: 'entscheidungstag' },
|
||||||
|
{ id: 'eu-werkstatt' },
|
||||||
|
{ id: 'farmer' },
|
||||||
|
{ id: 'fluggesellschaft' },
|
||||||
|
{ id: 'heli' },
|
||||||
|
{ id: 'logistik' },
|
||||||
|
{ id: 'sonnensystem', play2: true },
|
||||||
|
{ id: 'staustufen' },
|
||||||
|
{ id: 'tourismusregion' },
|
||||||
|
{ id: 'tourismustal' },
|
||||||
|
{ id: 'vulkan', play2: true },
|
||||||
|
{ id: 'wal' },
|
||||||
|
{ id: 'weltkueche' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ---- In-Page: EIN Onboarding-Schritt wegklicken -------------------------
|
||||||
|
async function skipStep(page) {
|
||||||
|
return await page.evaluate(() => {
|
||||||
|
// "loose": gerendert, aber darf auch unter dem Fold liegen (.click() geht trotzdem)
|
||||||
|
const rendered = (el) => {
|
||||||
|
if (!el) return false;
|
||||||
|
const r = el.getBoundingClientRect();
|
||||||
|
if (r.width < 6 || r.height < 6) return false;
|
||||||
|
const s = getComputedStyle(el);
|
||||||
|
return s.visibility !== 'hidden' && s.display !== 'none' && +s.opacity > 0.05
|
||||||
|
&& !el.disabled && el.getAttribute('aria-hidden') !== 'true'
|
||||||
|
&& !el.classList.contains('hidden');
|
||||||
|
};
|
||||||
|
const clean = (s) => (s || '').replace(/\s+/g, ' ').trim();
|
||||||
|
const norm = (s) => clean(s).replace(/[^\p{L}\p{N} ]+/gu, '').trim().toLowerCase();
|
||||||
|
|
||||||
|
// Namensfelder füllen
|
||||||
|
document.querySelectorAll('input[type=text],input[type=search],input:not([type])').forEach((i) => {
|
||||||
|
if (rendered(i) && !i.value) {
|
||||||
|
i.value = 'Demo';
|
||||||
|
i.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
|
i.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 1) Explizite Skip-Buttons (schließen das GANZE Tutorial auf einmal)
|
||||||
|
const skipSel = [
|
||||||
|
'#emOnbSkip', '#tutorial-skip', '#heliOnbSkip', '#snOnbSkip',
|
||||||
|
'[onclick*="skipTutorial"]', '[onclick*="closeTutorial"]',
|
||||||
|
'[onclick*="skipOnboarding"]', '[onclick*="skipHeliOnboarding"]',
|
||||||
|
];
|
||||||
|
for (const sel of skipSel) {
|
||||||
|
const el = [...document.querySelectorAll(sel)].find(rendered);
|
||||||
|
if (el) { el.click(); return 'skip:' + sel; }
|
||||||
|
}
|
||||||
|
// Skip per Text
|
||||||
|
{
|
||||||
|
const el = [...document.querySelectorAll('button,a,[role=button]')].find(
|
||||||
|
(e) => rendered(e) && /^(uberspringen|tutorial uberspringen|einfuhrung uberspringen|nicht mehr anzeigen)$/.test(norm(e.textContent))
|
||||||
|
);
|
||||||
|
if (el) { el.click(); return 'skiptext'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) "Weiter"/"Los geht's" NUR innerhalb eines sichtbaren Overlays
|
||||||
|
// (verhindert das Anklicken von Gameplay-Buttons wie Vulkan "Weiter →")
|
||||||
|
const overSel = '.ggs-modal-overlay,.ggs-overlay,.ggs-overlay-card,.sn-overlay,'
|
||||||
|
+ '.overlay,.overlay-card,.splash-overlay,.lg-splash-overlay,.modal,'
|
||||||
|
+ '.st-hint-overlay,.st-hint-modal,#vulk-overlay,#vulk-modal,.ggs-qi-back,'
|
||||||
|
+ '[id*="Onb"],[id*="onboard"],[id*="overlay" i],[id*="modal" i],'
|
||||||
|
+ '[class*="onb"],[class*="welcome"],[class*="tutorial"],[class*="intro"],'
|
||||||
|
+ '[class*="willkommen"],[class*="overlay" i],[class*="ggs-qi"]';
|
||||||
|
const overlays = [...document.querySelectorAll(overSel)].filter((o) => {
|
||||||
|
if (!rendered(o)) return false;
|
||||||
|
const r = o.getBoundingClientRect();
|
||||||
|
return r.width > 240 && r.height > 150;
|
||||||
|
});
|
||||||
|
const proceedRx = /^(weiter|weiter gehts|los gehts|los geht s|auf gehts|verstanden|alles klar|habe verstanden|fertig|los arbeiten|jetzt starten|jetzt loslegen|jetzt spielen|spiel starten|neue partie|partie starten|los|okay|ok|schliessen|start|starten|beginnen)$/;
|
||||||
|
for (const ov of overlays) {
|
||||||
|
const btns = [...ov.querySelectorAll('button,a,[role=button],.btn')].filter(rendered);
|
||||||
|
let el = btns.find((b) => proceedRx.test(norm(b.textContent)));
|
||||||
|
if (!el) el = btns.find((b) => /^(x|close)$/.test(norm(b.textContent)) || /(^|\s)(close|ggs-modal-close)(\s|$)/i.test(b.className));
|
||||||
|
if (el) { el.click(); return 'proceed:' + norm(el.textContent).slice(0, 24); }
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}).catch(() => null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Noch ein Onboarding-Overlay sichtbar? (Heuristik für den Report)
|
||||||
|
async function onboardingStillVisible(page) {
|
||||||
|
return await page.evaluate(() => {
|
||||||
|
const rx = /(willkommen|wusstest du|anleitung|was lerne ich|so funktioniert|einf(ü|u)hrung|beobachtungs-?aufgaben|dein kreislauf|uberspringen|überspringen|vulkanologie|observatorium|du erkundest die planeten|was tust du)/i;
|
||||||
|
const els = [...document.querySelectorAll('.overlay,.ggs-overlay,.ggs-modal-overlay,.sn-overlay,.st-hint-overlay,#vulk-overlay,[class*="onb"],[class*="tutorial"],[class*="intro"],[class*="welcome"],[class*="overlay" i]')];
|
||||||
|
return els.some((el) => {
|
||||||
|
const r = el.getBoundingClientRect();
|
||||||
|
if (r.width < 240 || r.height < 150) return false;
|
||||||
|
const s = getComputedStyle(el);
|
||||||
|
if (s.visibility === 'hidden' || s.display === 'none' || +s.opacity < 0.05 || el.classList.contains('hidden')) return false;
|
||||||
|
return rx.test((el.textContent || '').slice(0, 500));
|
||||||
|
});
|
||||||
|
}).catch(() => false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clickSel(page, sel) {
|
||||||
|
return await page.evaluate((sel) => {
|
||||||
|
const el = [...document.querySelectorAll(sel)].find((e) => {
|
||||||
|
const r = e.getBoundingClientRect();
|
||||||
|
const s = getComputedStyle(e);
|
||||||
|
return r.width > 4 && r.height > 4 && s.visibility !== 'hidden' && s.display !== 'none' && !e.classList.contains('hidden');
|
||||||
|
});
|
||||||
|
if (el) { el.click(); return true; }
|
||||||
|
return false;
|
||||||
|
}, sel).catch(() => false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clickByText(page, source) {
|
||||||
|
return await page.evaluate((src) => {
|
||||||
|
const rx = new RegExp(src, 'i');
|
||||||
|
const norm = (s) => (s || '').replace(/\s+/g, ' ').trim().replace(/[^\p{L}\p{N} ]+/gu, '').trim();
|
||||||
|
const el = [...document.querySelectorAll('button,a,[role=button],.btn')].find((e) => {
|
||||||
|
const r = e.getBoundingClientRect();
|
||||||
|
const s = getComputedStyle(e);
|
||||||
|
if (r.width < 6 || r.height < 6 || s.visibility === 'hidden' || s.display === 'none' || e.classList.contains('hidden')) return false;
|
||||||
|
return rx.test(norm(e.textContent));
|
||||||
|
});
|
||||||
|
if (el) { el.click(); return true; }
|
||||||
|
return false;
|
||||||
|
}, source).catch(() => false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function skipLoop(page, rounds = 12) {
|
||||||
|
const seq = [];
|
||||||
|
for (let i = 0; i < rounds; i++) {
|
||||||
|
const r = await skipStep(page);
|
||||||
|
if (!r) break;
|
||||||
|
seq.push(r);
|
||||||
|
await page.waitForTimeout(420);
|
||||||
|
}
|
||||||
|
return seq;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- pro-Sim: Auswahl-Screen betreten, dann Tutorial wegklicken ---------
|
||||||
|
const ENTRY = {
|
||||||
|
// heli: Flugkarte braucht PHP-injizierte Waypoints (WP), die beim direkten
|
||||||
|
// Aufruf von game.html fehlen (-> "WP is not defined"). Daher bleibt der
|
||||||
|
// stabile Gameplay-Screen die Einsatzzentrale (Missionsauswahl nach Onboarding).
|
||||||
|
'wal': async (page) => {
|
||||||
|
await clickSel(page, '.era-card-cta'); // "In dieser Welt starten →"
|
||||||
|
await page.waitForTimeout(1800);
|
||||||
|
await skipLoop(page);
|
||||||
|
},
|
||||||
|
'tourismusregion': async (page) => {
|
||||||
|
await clickSel(page, '#mapList .map-btn, .map-btn'); // Wegkarte wählen
|
||||||
|
await page.waitForTimeout(3000); // 3D-Szene
|
||||||
|
await skipLoop(page); // "Willkommen …" weg
|
||||||
|
},
|
||||||
|
'tourismustal': async (page) => {
|
||||||
|
await clickSel(page, '[data-scene]'); // Szene wählen
|
||||||
|
await page.waitForTimeout(3000);
|
||||||
|
await skipLoop(page); // "Die Gäste …" weg
|
||||||
|
},
|
||||||
|
'eu-werkstatt': async (page) => {
|
||||||
|
await clickSel(page, '.scenario-card'); // Szenario betreten
|
||||||
|
await page.waitForTimeout(1600);
|
||||||
|
await skipLoop(page, 8); // Konzept-Intro weg
|
||||||
|
await page.waitForTimeout(900);
|
||||||
|
await clickByText(page, 'los gehts|los geht|^weiter|verstanden|alles klar');
|
||||||
|
await page.waitForTimeout(700);
|
||||||
|
await skipLoop(page, 4);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Nach dem Wegklicken des Onboardings noch aufräumen, um die echte Szene zu zeigen.
|
||||||
|
const POST = {
|
||||||
|
'vulkan': async (page) => {
|
||||||
|
// "Los geht's" öffnet direkt das erste Aufgaben-Modal (#vulk-overlay).
|
||||||
|
// Schließen -> darunter liegt die 3D-Vulkanszene + Steuerpanel (Slider).
|
||||||
|
await page.evaluate(() => { const o = document.getElementById('vulk-overlay'); if (o) o.classList.remove('show'); });
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const PLAY2 = {
|
||||||
|
'vulkan': async (page) => { await clickSel(page, '#erupt-btn'); await page.waitForTimeout(3200); },
|
||||||
|
'sonnensystem': async (page) => { await clickSel(page, '#obs-open-btn'); await page.waitForTimeout(1800); await skipLoop(page, 4); await page.waitForTimeout(1200); },
|
||||||
|
'energiemanager': async (page) => { await clickSel(page, '#btnRunDay'); await page.waitForTimeout(3200); },
|
||||||
|
};
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const onlyArg = (process.argv.find((a) => a.startsWith('--only=')) || '').slice(7);
|
||||||
|
const vpArg = (process.argv.find((a) => a.startsWith('--vp=')) || '').slice(5);
|
||||||
|
const list = onlyArg ? SIMS.filter((s) => onlyArg.split(',').includes(s.id)) : SIMS;
|
||||||
|
const vps = vpArg ? VIEWPORTS.filter((v) => v.key === vpArg) : VIEWPORTS;
|
||||||
|
|
||||||
|
const browser = await chromium.launch({
|
||||||
|
args: ['--use-gl=swiftshader', '--ignore-gpu-blocklist', '--enable-unsafe-swiftshader'],
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const vp of vps) {
|
||||||
|
console.log(`\n===== ${vp.key} (${vp.w}x${vp.h}) =====`);
|
||||||
|
for (const sim of list) {
|
||||||
|
const ctx = await browser.newContext({ viewport: { width: vp.w, height: vp.h }, deviceScaleFactor: 1 });
|
||||||
|
const page = await ctx.newPage();
|
||||||
|
const errs = [];
|
||||||
|
page.on('pageerror', (e) => { if (errs.length < 3) errs.push('PE:' + e.message.slice(0, 50)); });
|
||||||
|
|
||||||
|
const url = `${BASE_URL}/${sim.id}/game.html`;
|
||||||
|
try {
|
||||||
|
await page.goto(url, { waitUntil: 'load', timeout: 40000 });
|
||||||
|
} catch (e) { errs.push('GOTO:' + e.message.slice(0, 40)); }
|
||||||
|
await page.waitForTimeout(3200); // 3D / CDN
|
||||||
|
|
||||||
|
let seq = await skipLoop(page, 14); // sofortiges Onboarding
|
||||||
|
if (ENTRY[sim.id]) await ENTRY[sim.id](page); // Auswahl-Screen + Tutorial
|
||||||
|
await page.waitForTimeout(2000);
|
||||||
|
// Zweiter Durchlauf: 3D-Sims (vulkan/sonnensystem) laden ihr Onboarding
|
||||||
|
// asynchron erst NACH dem ersten Skip -> hier erneut wegklicken.
|
||||||
|
seq = seq.concat(await skipLoop(page, 10));
|
||||||
|
if (POST[sim.id]) await POST[sim.id](page);
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
|
||||||
|
const stuck = await onboardingStillVisible(page);
|
||||||
|
let ok = false;
|
||||||
|
try {
|
||||||
|
await page.screenshot({ path: path.join(SHOTS_DIR, `${sim.id}__${vp.key}__play.png`) });
|
||||||
|
ok = true;
|
||||||
|
} catch (e) { errs.push('SHOT:' + e.message.slice(0, 40)); }
|
||||||
|
|
||||||
|
let ok2 = null;
|
||||||
|
if (sim.play2 && PLAY2[sim.id]) {
|
||||||
|
try {
|
||||||
|
await PLAY2[sim.id](page);
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
await page.screenshot({ path: path.join(SHOTS_DIR, `${sim.id}__${vp.key}__play2.png`) });
|
||||||
|
ok2 = true;
|
||||||
|
} catch (e) { ok2 = false; errs.push('SHOT2:' + e.message.slice(0, 40)); }
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
' ' + sim.id.padEnd(18)
|
||||||
|
+ (ok ? 'play OK' : 'play FAIL')
|
||||||
|
+ (ok2 === null ? '' : ok2 ? ' · play2 OK' : ' · play2 FAIL')
|
||||||
|
+ (stuck ? ' [!! ONBOARDING NOCH SICHTBAR]' : '')
|
||||||
|
+ ' clicks=' + seq.length + (seq.length ? ' (' + seq.slice(0, 6).join(',') + ')' : '')
|
||||||
|
+ (errs.length ? ' ' + errs.slice(0, 2).join(' | ') : '')
|
||||||
|
);
|
||||||
|
|
||||||
|
await ctx.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await browser.close();
|
||||||
|
console.log('\nFertig. Galerie: http://localhost/geograsim/App/.LocalDeveloperTools/display-preview/display_preview.html');
|
||||||
|
})();
|
||||||
|
After Width: | Height: | Size: 369 KiB |
|
After Width: | Height: | Size: 304 KiB |
|
After Width: | Height: | Size: 194 KiB |
|
After Width: | Height: | Size: 167 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 228 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 114 KiB |
|
After Width: | Height: | Size: 175 KiB |
|
After Width: | Height: | Size: 286 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 247 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 301 KiB |
|
After Width: | Height: | Size: 279 KiB |
|
After Width: | Height: | Size: 153 KiB |
|
After Width: | Height: | Size: 167 KiB |
|
After Width: | Height: | Size: 492 KiB |
|
After Width: | Height: | Size: 316 KiB |
|
After Width: | Height: | Size: 303 KiB |
|
After Width: | Height: | Size: 170 KiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 721 KiB |
|
After Width: | Height: | Size: 505 KiB |
|
After Width: | Height: | Size: 316 KiB |
|
After Width: | Height: | Size: 225 KiB |
|
After Width: | Height: | Size: 240 KiB |
|
After Width: | Height: | Size: 242 KiB |
|
After Width: | Height: | Size: 218 KiB |
|
After Width: | Height: | Size: 164 KiB |
|
After Width: | Height: | Size: 454 KiB |
|
After Width: | Height: | Size: 306 KiB |
|
After Width: | Height: | Size: 277 KiB |
|
After Width: | Height: | Size: 189 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 184 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 147 KiB |
|
After Width: | Height: | Size: 266 KiB |
|
After Width: | Height: | Size: 264 KiB |
|
After Width: | Height: | Size: 177 KiB |
|
After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 192 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 160 KiB |
|
After Width: | Height: | Size: 714 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 444 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 485 KiB |
|
After Width: | Height: | Size: 595 KiB |
|
After Width: | Height: | Size: 298 KiB |
|
After Width: | Height: | Size: 332 KiB |
|
After Width: | Height: | Size: 413 KiB |
|
After Width: | Height: | Size: 238 KiB |
|
After Width: | Height: | Size: 391 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 178 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 601 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 500 KiB |
|
After Width: | Height: | Size: 79 KiB |
@@ -0,0 +1,272 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>GeoGraSim — Test-Checkliste (19.08.2026)</title>
|
||||||
|
<style>
|
||||||
|
:root{
|
||||||
|
--bg:#f4f2ec; --card:#fff; --ink:#242018; --muted:#7a7266; --line:rgba(0,0,0,.08);
|
||||||
|
--accent:#4a7c8a; --accent-d:#356070; --ok:#4a8a5a; --warn:#c58a2a; --sim:#7a5ca8;
|
||||||
|
}
|
||||||
|
*{margin:0;padding:0;box-sizing:border-box}
|
||||||
|
body{font-family:'Inter',system-ui,-apple-system,sans-serif;background:var(--bg);color:var(--ink);line-height:1.5;padding-bottom:4rem}
|
||||||
|
header{position:sticky;top:0;z-index:10;background:#fff;border-bottom:1px solid var(--line);
|
||||||
|
padding:.9rem 1.2rem;display:flex;align-items:center;gap:1rem;flex-wrap:wrap;box-shadow:0 1px 6px rgba(0,0,0,.04)}
|
||||||
|
header h1{font-size:1.15rem;font-weight:900;color:var(--accent-d);letter-spacing:-.02em}
|
||||||
|
header .sub{font-size:.72rem;color:var(--muted)}
|
||||||
|
.progress{margin-left:auto;display:flex;align-items:center;gap:.6rem;font-size:.8rem;font-weight:700;color:var(--accent-d)}
|
||||||
|
.bar{width:160px;height:9px;border-radius:99px;background:#e4e0d6;overflow:hidden}
|
||||||
|
.bar > div{height:100%;background:var(--ok);width:0;transition:width .3s}
|
||||||
|
.reset{font-size:.66rem;color:var(--muted);border:1px solid var(--line);background:#fff;border-radius:6px;padding:.25rem .5rem;cursor:pointer}
|
||||||
|
.wrap{max-width:920px;margin:1.4rem auto;padding:0 1.1rem}
|
||||||
|
.intro{font-size:.86rem;color:var(--muted);margin-bottom:1.4rem;background:#fff;border:1px solid var(--line);border-radius:10px;padding:.8rem 1rem}
|
||||||
|
.intro code{background:#eee9df;padding:.05rem .35rem;border-radius:4px;font-size:.85em}
|
||||||
|
section{margin-bottom:1.8rem}
|
||||||
|
.sec-h{font-size:.7rem;font-weight:800;text-transform:uppercase;letter-spacing:.1em;color:var(--accent);
|
||||||
|
margin:0 0 .6rem .2rem;display:flex;align-items:center;gap:.5rem}
|
||||||
|
.sec-h .cnt{font-size:.62rem;color:var(--muted);font-weight:600;letter-spacing:.02em}
|
||||||
|
.item{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:.85rem 1rem;margin-bottom:.7rem;
|
||||||
|
box-shadow:0 1px 3px rgba(0,0,0,.03);transition:opacity .2s}
|
||||||
|
.item.done{opacity:.55}
|
||||||
|
.item-top{display:flex;gap:.7rem;align-items:flex-start}
|
||||||
|
.item-top input[type=checkbox]{width:22px;height:22px;margin-top:.1rem;accent-color:var(--ok);cursor:pointer;flex-shrink:0}
|
||||||
|
.item-title{font-weight:800;font-size:.98rem}
|
||||||
|
.item.done .item-title{text-decoration:line-through;text-decoration-color:rgba(0,0,0,.3)}
|
||||||
|
.tag{display:inline-block;font-size:.58rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;
|
||||||
|
padding:.12rem .5rem;border-radius:99px;vertical-align:middle;margin-left:.5rem}
|
||||||
|
.tag.live{background:#dceadd;color:#2f6b3a}
|
||||||
|
.tag.pending{background:#fdeecd;color:#8a6510}
|
||||||
|
.body{padding-left:calc(22px + .7rem);margin-top:.4rem;font-size:.86rem;color:#4a463d}
|
||||||
|
.body .row{margin:.28rem 0}
|
||||||
|
.body b{color:var(--ink)}
|
||||||
|
.k{display:inline-block;min-width:74px;font-weight:700;color:var(--muted);font-size:.72rem;text-transform:uppercase;letter-spacing:.03em}
|
||||||
|
a.url{color:var(--accent-d);font-family:ui-monospace,monospace;font-size:.82em;text-decoration:none;border-bottom:1px dotted var(--accent)}
|
||||||
|
a.url:hover{background:#eaf2f4}
|
||||||
|
.exp{background:#f0f6f1;border-left:3px solid var(--ok);border-radius:0 6px 6px 0;padding:.35rem .6rem;margin-top:.35rem;font-size:.82rem;color:#33613f}
|
||||||
|
.note{width:100%;margin-top:.5rem;border:1px solid var(--line);border-radius:7px;padding:.4rem .55rem;font-family:inherit;
|
||||||
|
font-size:.82rem;resize:vertical;min-height:0;height:2rem;background:#fcfbf8;color:var(--ink)}
|
||||||
|
.note::placeholder{color:#b3ac9e}
|
||||||
|
footer{text-align:center;color:var(--muted);font-size:.72rem;margin:2rem 0}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<div>
|
||||||
|
<h1>🧪 GeoGraSim — Test-Checkliste</h1>
|
||||||
|
<div class="sub">Alle Erledigungen seit den Weltküche-Lebensmitteln · Stand 19.08.2026 · lokal testen</div>
|
||||||
|
</div>
|
||||||
|
<div class="progress">
|
||||||
|
<span id="pcount">0 / 0</span>
|
||||||
|
<div class="bar"><div id="pbar"></div></div>
|
||||||
|
<button class="reset" onclick="if(confirm('Alle Haken + Notizen zurücksetzen?')){localStorage.clear();location.reload();}">reset</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="wrap">
|
||||||
|
<div class="intro">
|
||||||
|
Basis-URL lokal: <code>http://localhost/geograsim/App/</code> · <b>Haken & Notizen</b> werden im Browser gespeichert.
|
||||||
|
<span class="tag live">live</span> = auf Prod, <span class="tag pending">bereit</span> = committed, kommt mit dem nächsten Deploy.
|
||||||
|
Für Admin-Seiten zuerst über <a class="url" href="http://localhost/geograsim/App/admin.html" target="_blank">admin.html</a> anmelden (Username <b>Thomas</b> → PIN ist lokal vorausgefüllt).
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ================= WELTKÜCHE (SIM) ================= -->
|
||||||
|
<section data-sec="Weltküche – Simulation"></section>
|
||||||
|
|
||||||
|
<!-- ================= WELTKÜCHE (LEHRPLAN/ADMIN) ================= -->
|
||||||
|
<section data-sec="Weltküche – Lehrplan & Admin"></section>
|
||||||
|
|
||||||
|
<!-- ================= LEHRER-COCKPIT ================= -->
|
||||||
|
<section data-sec="Lehrer-Cockpit (teacher.html)"></section>
|
||||||
|
|
||||||
|
<!-- ================= TOURISMUSREGION ================= -->
|
||||||
|
<section data-sec="Tourismusregion"></section>
|
||||||
|
|
||||||
|
<!-- ================= LOGISTIK & PLATTFORM ================= -->
|
||||||
|
<section data-sec="Logistik & Plattform"></section>
|
||||||
|
|
||||||
|
<!-- ================= ADMIN & AUTH ================= -->
|
||||||
|
<section data-sec="Admin & Auth-Fundament"></section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>GeoGraSim · Test-Checkliste · lokal generiert 19.08.2026</footer>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Alle Test-Items als Daten — daraus wird die Liste gebaut.
|
||||||
|
const BASE = 'http://localhost/geograsim/App/';
|
||||||
|
function u(path){ return BASE + path; }
|
||||||
|
const ITEMS = {
|
||||||
|
'Weltküche – Simulation': [
|
||||||
|
{ id:'wk-artikel', t:'Korrekte Artikel für alle 70 Zutaten', tag:'live',
|
||||||
|
w:'der/die/das je Zutat („die Kartoffel", „der Pfeffer", „das Olivenöl") statt Platzhalter.',
|
||||||
|
url:'sims/weltkueche/game.html', steps:'Ein Gericht spielen, Zutat lösen/falsch klicken – im Feedback-Satz steht der richtige Artikel.',
|
||||||
|
exp:'Nie mehr „der/die/das Kartoffel" – immer „die Kartoffel" usw.' },
|
||||||
|
{ id:'wk-ukraine', t:'Verdrehter Herkunfts-Satz korrigiert (Ukraine-Fall)', tag:'live',
|
||||||
|
w:'Bei fernem Großproduzenten (z. B. Ukraine-Kartoffel im AT-Gericht) sachlich korrektes Feedback.',
|
||||||
|
url:'sims/weltkueche/game.html', steps:'Tiroler Gröstl → Kartoffel → ein fernes Land wie Ukraine anklicken.',
|
||||||
|
exp:'„…in unseren Handel kommt davon kaum etwas… nimmt man die Kartoffel aus Österreich oder einem Nachbarland." (nicht mehr „…stammt die Kartoffel von dort").' },
|
||||||
|
{ id:'wk-heimat', t:'Heimat-Banner neu formuliert', tag:'live',
|
||||||
|
w:'Erklärt „bevorzugt aus AT, aber AT deckt den Bedarf nicht allein".',
|
||||||
|
url:'sims/weltkueche/game.html', steps:'AT-Gericht, eine Heimat-Zutat lösen → grünes Banner lesen.',
|
||||||
|
exp:'„Zutaten werden zwar bevorzugt aus Österreich verwendet, aber Österreich allein deckt den Bedarf nicht ab – darum zählt auch ein Nachbarland."' },
|
||||||
|
{ id:'wk-gravitation', t:'Herkunft per Handels-Gravitation (Nähe × Menge)', tag:'pending',
|
||||||
|
w:'Zweit-Lieferant = nächster großer Nachbar, nicht ferner Großproduzent.',
|
||||||
|
url:'sims/weltkueche/game.html', steps:'Ein AT-Gericht mit Eiern/Kartoffel spielen – richtige Länder prüfen.',
|
||||||
|
exp:'AT-Eier → Österreich + Deutschland (NICHT Frankreich), obwohl FR mehr Eier erzeugt.' },
|
||||||
|
{ id:'wk-punkte', t:'Punktefix: „alle Falschen zuerst = 0"', tag:'pending',
|
||||||
|
w:'Strafe pro Fehlklick = 100 ÷ Anzahl Distraktoren.',
|
||||||
|
url:'sims/weltkueche/game.html', steps:'Bei einer Zutat ALLE falschen Länder zuerst anklicken, dann die richtigen.',
|
||||||
|
exp:'Ergebnis 0 Punkte. Banner zeigt die tatsächliche Strafe pro Fehlklick.' },
|
||||||
|
{ id:'wk-phase', t:'Live-State zeigt echten Fortschritt statt „forschen"', tag:'live',
|
||||||
|
w:'GGS_LIVE_STATE-Phase = „Gericht X · Zutat Y/Z".',
|
||||||
|
url:'teacher.html', steps:'Als Lehrer im Cockpit „Aktuell" ein Weltküche-Kind ansehen (oder Live-State im Sim prüfen).',
|
||||||
|
exp:'Phase/Info zeigt „Gericht 2 · Zutat 3/5" – nicht dauerhaft „forschen".' },
|
||||||
|
{ id:'wk-submit', t:'Backend-Submit + Persistenz geschaffter Gerichte', tag:'live',
|
||||||
|
w:'Pro fertigem Gericht ein assessments-Eintrag; geschaffte Gerichte bleiben nach Reload.',
|
||||||
|
url:'sims/weltkueche/game.html', steps:'Ein Gericht abschließen, Seite neu laden.',
|
||||||
|
exp:'Bereits geschaffte Gerichte bleiben erkennbar; im Lehrer-Cockpit taucht das Ergebnis auf.' },
|
||||||
|
],
|
||||||
|
'Weltküche – Lehrplan & Admin': [
|
||||||
|
{ id:'wk-lehrplan', t:'Lehrplan-Bezug auf der Weltküche-Modulseite', tag:'live',
|
||||||
|
w:'Kompetenz „Welternährung, Konsum und globale Disparitäten" + Anker.',
|
||||||
|
url:'modul-weltkueche', steps:'Seite öffnen, zu „Lehrplan-Bezug" scrollen.',
|
||||||
|
exp:'Kompetenzen + Anker sichtbar (nicht mehr „Noch keine Kompetenzen zugeordnet").' },
|
||||||
|
{ id:'wk-filter', t:'Länder-Filter im Lehrplan-Bezug wirkt', tag:'live',
|
||||||
|
w:'Filter-Chips (Alle/AT/DE/CH/FL) blenden Fach-Anker wirklich um.',
|
||||||
|
url:'modul-weltkueche', steps:'Bei „Lehrplan-Bezug" auf Deutschland, dann Österreich klicken.',
|
||||||
|
exp:'Sichtbare Fach-Anker ändern sich je Land (vorher passierte nichts).' },
|
||||||
|
{ id:'wk-admin', t:'Admin-QA: Weltküche-Herkunftsübersicht (NEU)', tag:'pending',
|
||||||
|
w:'Pro Gericht × Zutat richtige Länder + Distraktoren; „nur Auffällige"-Filter.',
|
||||||
|
url:'admin-weltkueche.html', steps:'Als Admin einloggen, Seite öffnen. Suche/Kategorie testen, „nur Auffällige" aktivieren.',
|
||||||
|
exp:'30 Gerichte; Kartoffel/Eier → 🟢 AT+DE. „Nur Auffällige" zeigt ~5 Kandidaten zum Prüfen.' },
|
||||||
|
],
|
||||||
|
'Lehrer-Cockpit (teacher.html)': [
|
||||||
|
{ id:'tc-menu', t:'Menü „Live" → „Ergebnisse" mit Tag · Aktuell · Vergleich · Gesamt', tag:'pending',
|
||||||
|
w:'Umbenannt + 4. Unteransicht „Gesamt" (alte Ergebnis-Ansicht).',
|
||||||
|
url:'teacher.html', steps:'Als Lehrer einloggen, Klasse wählen, Tab „Ergebnisse".',
|
||||||
|
exp:'Vier Unteransichten; „Gesamt" zeigt die frühere Ergebnis-/Auswertungsansicht.' },
|
||||||
|
{ id:'tc-quali', t:'Qualitäts-Kennzahl (richtig ↔ falsch als Balken)', tag:'pending',
|
||||||
|
w:'Grafischer Balken (grün/rot) je Kind in Aktuell + Vergleich.',
|
||||||
|
url:'teacher.html', steps:'Ergebnisse → Aktuell/Vergleich bei aktivem Weltküche-Kind.',
|
||||||
|
exp:'Zweifarbiger Balken + %-Wert („blödeln vs. gut" sichtbar).' },
|
||||||
|
{ id:'tc-labels', t:'Klartext-Labels statt Jargon', tag:'pending',
|
||||||
|
w:'„Stark/Solide/Viele Fehler" statt „Kritisch/OK"; „Gerade dabei" statt „Phase".',
|
||||||
|
url:'teacher.html', steps:'Aktuell/Vergleich ansehen.',
|
||||||
|
exp:'Keine „Kritisch"-Bezeichnung mehr.' },
|
||||||
|
{ id:'tc-simlabels', t:'Sim-Bezeichner: Wort + 3-Buchstaben-Kürzel', tag:'pending',
|
||||||
|
w:'Kein „Log L1" mehr — enger Balken zeigt Kürzel (LOG), breiter das Wort.',
|
||||||
|
url:'teacher.html', steps:'Ergebnisse → Tag, Session-Balken ansehen.',
|
||||||
|
exp:'Balken tragen LOG/Logistik statt „Log L1".' },
|
||||||
|
{ id:'tc-logistik', t:'Logistik hat kuratierte Spalten (statt Roh-Dump)', tag:'pending',
|
||||||
|
w:'Stufe · Budget · Geliefert · Unterwegs · Offen · Verspätet · Fortschritt.',
|
||||||
|
url:'teacher.html', steps:'Ergebnisse → Aktuell mit aktivem Logistik-Kind.',
|
||||||
|
exp:'Sinnvolle Spalten – keine simId/__activeMs/timestamp-Spalten mehr.' },
|
||||||
|
{ id:'tc-streifen', t:'Aktivitätsstreifen: Farbe je Sim, Höhe=Erfolg, Rahmen=Zustand', tag:'pending',
|
||||||
|
w:'Fertig = geschlossener Rahmen · läuft = rechts offen · Abbruch = gestrichelt + ✖.',
|
||||||
|
url:'teacher.html', steps:'Ergebnisse → Tag, mit Sessions eines Tages.',
|
||||||
|
exp:'Balken je Sim gefärbt, höher = besser; Rahmen/✖ zeigen den Zustand; Legende erklärt es.' },
|
||||||
|
{ id:'tc-assess', t:'assessment-Skip-Fix (keine leeren Ping-Zeilen mehr)', tag:'pending',
|
||||||
|
w:'„started/running"-Pings landen nicht mehr als leere Zeilen in Ergebnisse/Tag.',
|
||||||
|
url:'teacher.html', steps:'Nach etwas Spielzeit Ergebnisse/Tag ansehen.',
|
||||||
|
exp:'Nur echte Abschlüsse als Balken – kein leeres Rauschen.' },
|
||||||
|
],
|
||||||
|
'Tourismusregion': [
|
||||||
|
{ id:'tr-start', t:'Startseite: keine Name/Klasscode-Felder, Klick auf Route startet', tag:'live',
|
||||||
|
w:'Felder entfernt; Klick auf eine Wegkarte startet sofort (kein „Partie starten").',
|
||||||
|
url:'sims/tourismusregion/game.html', steps:'Sim öffnen, eine Wegkarte anklicken.',
|
||||||
|
exp:'Keine Eingabefelder; Partie startet direkt bei Kartenklick.' },
|
||||||
|
{ id:'tr-ipad', t:'iPad-Responsive (Panels decken die Karte nicht mehr zu)', tag:'live',
|
||||||
|
w:'Panels mit Höhen-Deckel + eigene iPad-Landscape-Regeln. AM IPAD TESTEN!',
|
||||||
|
url:'sims/tourismusregion/game.html', steps:'Am echten iPad (Landscape) öffnen und ein Gebäude antippen.',
|
||||||
|
exp:'KPIs/Baumenü/Info-Panel überdecken die Karte nicht; Info-Panel scrollt statt zu überziehen.' },
|
||||||
|
{ id:'tr-lift', t:'Seilbahn hat eine Talstation (nicht mehr „transparent")', tag:'live',
|
||||||
|
w:'Talstation mit Dach + Umlenkrad unten am Lift.',
|
||||||
|
url:'sims/tourismusregion/game.html', steps:'Seilbahn bauen (Berg erschließen), auf die Talstation schauen.',
|
||||||
|
exp:'Solider Baukörper unten – kein durchsichtiger Lift-Anfang.' },
|
||||||
|
{ id:'tr-save', t:'Abschluss: kein „Ergebnis speichern"-Button (Auto-Save)', tag:'live',
|
||||||
|
w:'Automatisches Speichern am Partie-Ende; Button entfernt.',
|
||||||
|
url:'sims/tourismusregion/game.html', steps:'Eine Partie zu Ende bringen.',
|
||||||
|
exp:'Hinweis „automatisch gespeichert", kein Speichern-Button.' },
|
||||||
|
{ id:'tr-pass', t:'Neue Karte „Passstraße"', tag:'live',
|
||||||
|
w:'Route bergauf (136 Punkte) + 41 Bauslots + Hintergrundbild.',
|
||||||
|
url:'sims/tourismusregion/game.html', steps:'Im Startbildschirm „Passstraße" wählen.',
|
||||||
|
exp:'Karte lädt, Route + Bauslots passen aufs Bild (optisch prüfen).' },
|
||||||
|
{ id:'tr-balance', t:'Balance: Umwelt beißt jetzt (Übertourismus)', tag:'pending',
|
||||||
|
w:'Zu viel Bauen + Verkehr senken die Umwelt spürbar; Umwelt-Malus wirkt.',
|
||||||
|
url:'sims/tourismusregion/game.html', steps:'Viel/dicht bauen und laufen lassen; Umwelt-KPI beobachten.',
|
||||||
|
exp:'Umwelt fällt deutlich (Richtung <45), umweltbewusste Gäste werden unzufriedener.' },
|
||||||
|
{ id:'tr-erklaer', t:'Schüler-Erklärung: Umwelt & Naturschutzgebiet', tag:'pending',
|
||||||
|
w:'Willkommenskarte + Warnkarte bei Umwelt<50 + klarere Naturschutz-Beschreibung.',
|
||||||
|
url:'sims/tourismusregion/game.html', steps:'Neu starten → Willkommenskarte lesen; Umwelt absacken lassen → Warnkarte.',
|
||||||
|
exp:'Karte erklärt Umwelt-Mechanik + Naturschutzgebiet; Warnkarte erscheint bei <50.' },
|
||||||
|
],
|
||||||
|
'Logistik & Plattform': [
|
||||||
|
{ id:'lg-pause', t:'Logistik-Pause friert die Aktivzeit ein', tag:'pending',
|
||||||
|
w:'Bei Pause zählt die „Seit"-Zeit im Cockpit nicht weiter.',
|
||||||
|
url:'sims/logistik/game.html', steps:'Logistik spielen, ⏸ drücken, im Lehrer-Cockpit die Aktivzeit beobachten.',
|
||||||
|
exp:'Aktivzeit steht still, solange pausiert; läuft nach dem Fortsetzen weiter.' },
|
||||||
|
{ id:'pf-scripts', t:'Wrapper-Bug behoben (/teacher lud kein Script mehr)', tag:'live',
|
||||||
|
w:'Favicon wurde ins letzte </head> injiziert – zerschoss ein JS-Popup-Script.',
|
||||||
|
url:'teacher', steps:'Die /teacher-Route (Wrapper) öffnen.',
|
||||||
|
exp:'Seite lädt sauber, keine „Invalid token"-Konsolenfehler. (teacher.html direkt lief immer.)' },
|
||||||
|
],
|
||||||
|
'Admin & Auth-Fundament': [
|
||||||
|
{ id:'ad-login', t:'Lokaler Admin-Login ohne Mailversand (Dev-PIN)', tag:'pending',
|
||||||
|
w:'PIN wird lokal vorausgefüllt; Mailer-Fehler sperrt nicht mehr aus. Auf Prod bleibt 2FA-Mail Pflicht.',
|
||||||
|
url:'admin.html', steps:'Username Thomas + Passwort → „Anmelden".',
|
||||||
|
exp:'PIN-Feld ist vorausgefüllt („Dev: … vorausgefüllt"), „Bestätigen" → eingeloggt.' },
|
||||||
|
{ id:'auth-found', t:'Auth-Fundament JWT + Redis (schlafend, non-breaking)', tag:'pending',
|
||||||
|
w:'firebase/php-jwt + predis + Jwt/SessionStore + Session-Kanalisierung. AUTH_BACKEND=session.',
|
||||||
|
url:'', steps:'Kein direkter Test nötig — nur sicherstellen, dass Login/Admin normal funktionieren.',
|
||||||
|
exp:'Alles läuft wie bisher (das Fundament ist inaktiv, bis der JWT-Cutover kommt).' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const listWrap = document.querySelector('.wrap');
|
||||||
|
document.querySelectorAll('section[data-sec]').forEach(sec => {
|
||||||
|
const name = sec.getAttribute('data-sec');
|
||||||
|
const items = ITEMS[name] || [];
|
||||||
|
let html = '<div class="sec-h">' + name + ' <span class="cnt">' + items.length + ' Punkte</span></div>';
|
||||||
|
items.forEach(it => {
|
||||||
|
const urlHtml = it.url ? '<a class="url" href="' + u(it.url) + '" target="_blank" rel="noopener">' + u(it.url) + '</a>' : '<span style="color:#b3ac9e">—</span>';
|
||||||
|
html += ''
|
||||||
|
+ '<div class="item" data-id="' + it.id + '">'
|
||||||
|
+ '<div class="item-top">'
|
||||||
|
+ '<input type="checkbox" data-check="' + it.id + '">'
|
||||||
|
+ '<div style="flex:1"><span class="item-title">' + it.t + '</span>'
|
||||||
|
+ '<span class="tag ' + it.tag + '">' + (it.tag==='live'?'live':'bereit') + '</span></div>'
|
||||||
|
+ '</div>'
|
||||||
|
+ '<div class="body">'
|
||||||
|
+ '<div class="row"><span class="k">Was</span> ' + it.w + '</div>'
|
||||||
|
+ '<div class="row"><span class="k">Öffnen</span> ' + urlHtml + '</div>'
|
||||||
|
+ '<div class="row"><span class="k">Testen</span> ' + it.steps + '</div>'
|
||||||
|
+ '<div class="exp">✓ Erwartet: ' + it.exp + '</div>'
|
||||||
|
+ '<textarea class="note" data-note="' + it.id + '" placeholder="Notiz / gefundene Abweichung…"></textarea>'
|
||||||
|
+ '</div>'
|
||||||
|
+ '</div>';
|
||||||
|
});
|
||||||
|
sec.innerHTML = html;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Persistenz + Fortschritt
|
||||||
|
const checks = document.querySelectorAll('input[data-check]');
|
||||||
|
function refresh(){
|
||||||
|
let done = 0;
|
||||||
|
checks.forEach(c => { if(c.checked){ done++; c.closest('.item').classList.add('done'); } else c.closest('.item').classList.remove('done'); });
|
||||||
|
document.getElementById('pcount').textContent = done + ' / ' + checks.length;
|
||||||
|
document.getElementById('pbar').style.width = (checks.length? (done/checks.length*100):0) + '%';
|
||||||
|
}
|
||||||
|
checks.forEach(c => {
|
||||||
|
const key = 'chk_' + c.getAttribute('data-check');
|
||||||
|
c.checked = localStorage.getItem(key) === '1';
|
||||||
|
c.addEventListener('change', () => { localStorage.setItem(key, c.checked?'1':'0'); refresh(); });
|
||||||
|
});
|
||||||
|
document.querySelectorAll('textarea[data-note]').forEach(t => {
|
||||||
|
const key = 'note_' + t.getAttribute('data-note');
|
||||||
|
t.value = localStorage.getItem(key) || '';
|
||||||
|
t.addEventListener('input', () => localStorage.setItem(key, t.value));
|
||||||
|
});
|
||||||
|
refresh();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -173,7 +173,7 @@ var SIMS = [
|
|||||||
start_population:{min:1000,max:20000,def:6000,label:'Start-Bevölkerung'},
|
start_population:{min:1000,max:20000,def:6000,label:'Start-Bevölkerung'},
|
||||||
income_per_10k:{min:50,max:500,def:220,label:'Einnahmen pro 10.000 Bürger'},
|
income_per_10k:{min:50,max:500,def:220,label:'Einnahmen pro 10.000 Bürger'},
|
||||||
budget_loss:{min:-2000,max:0,def:-500,label:'Pleite-Grenze (Mio €, negativ)'},
|
budget_loss:{min:-2000,max:0,def:-500,label:'Pleite-Grenze (Mio €, negativ)'},
|
||||||
event_frequency:{min:0,max:1,def:0.3,step:0.1,label:'Ereignis-Häufigkeit (0–1)'},
|
event_frequency:{min:0,max:1,def:0.3,step:0.1,label:'Ereignis-Häufigkeit (0 – 1)'},
|
||||||
time_limit:{min:30,max:150,def:75,label:'Simulationsdauer (Runden)'}
|
time_limit:{min:30,max:150,def:75,label:'Simulationsdauer (Runden)'}
|
||||||
}},
|
}},
|
||||||
{id:'sim-05', name:'Klimawächter 2D (V1)', icon:'🌡️', img:'assets/img/card-climate.webp',
|
{id:'sim-05', name:'Klimawächter 2D (V1)', icon:'🌡️', img:'assets/img/card-climate.webp',
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
* Icons sind sauber generierte Emblems (Flat-Scandinavian, keine Emojis) unter
|
* Icons sind sauber generierte Emblems (Flat-Scandinavian, keine Emojis) unter
|
||||||
* assets/img/badges/. Gesperrte Abzeichen bleiben sichtbar (Zielvorstellung),
|
* assets/img/badges/. Gesperrte Abzeichen bleiben sichtbar (Zielvorstellung),
|
||||||
* die Beschreibung ist das Ziel. Sterne laufen parallel; ggsErfolgToStars()
|
* die Beschreibung ist das Ziel. Sterne laufen parallel; ggsErfolgToStars()
|
||||||
* bildet den normierten Erfolg (0–100) auf 0–5 Sterne ab.
|
* bildet den normierten Erfolg (0 – 100) auf 0 – 5 Sterne ab.
|
||||||
*
|
*
|
||||||
* progressObj (pro Sim): { plays, best_stars, level }
|
* progressObj (pro Sim): { plays, best_stars, level }
|
||||||
* ==========================================================================*/
|
* ==========================================================================*/
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
const MONSOON = [5, 6, 7, 8];
|
const MONSOON = [5, 6, 7, 8];
|
||||||
|
|
||||||
// Regen pro Monat in mm.
|
// Regen pro Monat in mm.
|
||||||
// Normal: Mumbai (Colaba) 1981–2010, India Meteorological Department.
|
// Normal: Mumbai (Colaba) 1981 – 2010, India Meteorological Department.
|
||||||
const NORMAL = [1, 0, 1, 0, 16, 506, 769, 472, 356, 82, 9, 3];
|
const NORMAL = [1, 0, 1, 0, 16, 506, 769, 472, 356, 82, 9, 3];
|
||||||
// Schwacher Monsun (Dürre-Jahr): Spitzen-Monate stark reduziert.
|
// Schwacher Monsun (Dürre-Jahr): Spitzen-Monate stark reduziert.
|
||||||
const WEAK = [1, 0, 1, 0, 10, 280, 420, 240, 180, 60, 6, 2];
|
const WEAK = [1, 0, 1, 0, 10, 280, 420, 240, 180, 60, 6, 2];
|
||||||
|
|||||||
@@ -322,7 +322,7 @@
|
|||||||
svgEl('text', { x: 450, y: 22, 'text-anchor': 'middle', 'font-size': '12', 'font-weight': '800', fill: '#1f4e5a' }, svg).textContent = 'Zusammengelegt';
|
svgEl('text', { x: 450, y: 22, 'text-anchor': 'middle', 'font-size': '12', 'font-weight': '800', fill: '#1f4e5a' }, svg).textContent = 'Zusammengelegt';
|
||||||
const mergedG = svgEl('g', {}, svg);
|
const mergedG = svgEl('g', {}, svg);
|
||||||
|
|
||||||
// Balken: Stückkosten je Gast (Skala 0–50 €, Höhe 110 px)
|
// Balken: Stückkosten je Gast (Skala 0 – 50 €, Höhe 110 px)
|
||||||
const BAR_BASE = 240, BAR_MAXH = 110, SCALE = 50;
|
const BAR_BASE = 240, BAR_MAXH = 110, SCALE = 50;
|
||||||
svgEl('line', { x1: 30, y1: BAR_BASE, x2: 570, y2: BAR_BASE, stroke: '#b5c9cf', 'stroke-width': '1.5' }, svg);
|
svgEl('line', { x1: 30, y1: BAR_BASE, x2: 570, y2: BAR_BASE, stroke: '#b5c9cf', 'stroke-width': '1.5' }, svg);
|
||||||
// Referenzbalken links: 45 € (statisch)
|
// Referenzbalken links: 45 € (statisch)
|
||||||
@@ -500,9 +500,9 @@
|
|||||||
};
|
};
|
||||||
// Farbzonen
|
// Farbzonen
|
||||||
[
|
[
|
||||||
{ a1: -80, a2: -40, c: '#c85c4a' }, // 49,80–49,90
|
{ a1: -80, a2: -40, c: '#c85c4a' }, // 49,80 – 49,90
|
||||||
{ a1: -40, a2: -20, c: '#e8833a' }, // 49,90–49,95
|
{ a1: -40, a2: -20, c: '#e8833a' }, // 49,90 – 49,95
|
||||||
{ a1: -20, a2: 20, c: '#5a8a5e' }, // 49,95–50,05
|
{ a1: -20, a2: 20, c: '#5a8a5e' }, // 49,95 – 50,05
|
||||||
{ a1: 20, a2: 40, c: '#e8833a' },
|
{ a1: 20, a2: 40, c: '#e8833a' },
|
||||||
{ a1: 40, a2: 80, c: '#c85c4a' }
|
{ a1: 40, a2: 80, c: '#c85c4a' }
|
||||||
].forEach(z => svgEl('path', { d: arcPath(z.a1, z.a2, R), stroke: z.c, 'stroke-width': '20', fill: 'none', 'stroke-linecap': 'butt', opacity: '0.9' }, svg));
|
].forEach(z => svgEl('path', { d: arcPath(z.a1, z.a2, R), stroke: z.c, 'stroke-width': '20', fill: 'none', 'stroke-linecap': 'butt', opacity: '0.9' }, svg));
|
||||||
@@ -599,7 +599,7 @@
|
|||||||
<div class="ge-gh-pill" id="ge-ps-status" data-state="now">Das Unterbecken ist voll — pumpe Wasser hinauf!</div>
|
<div class="ge-gh-pill" id="ge-ps-status" data-state="now">Das Unterbecken ist voll — pumpe Wasser hinauf!</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="ge-note">Pumpspeicherkraftwerke sind bis heute der einzige großtechnische
|
<p class="ge-note">Pumpspeicherkraftwerke sind bis heute der einzige großtechnische
|
||||||
Stromspeicher: Aus 100 kWh Pumpstrom kommen rund 75–80 kWh wieder heraus
|
Stromspeicher: Aus 100 kWh Pumpstrom kommen rund 75 – 80 kWh wieder heraus
|
||||||
(Gesamtwirkungsgrad, hier gerechnet mit 78 %). Quellen: VERBUND (Malta- und
|
(Gesamtwirkungsgrad, hier gerechnet mit 78 %). Quellen: VERBUND (Malta- und
|
||||||
Kaprun-Kraftwerke) · Fraunhofer ISE.</p>
|
Kaprun-Kraftwerke) · Fraunhofer ISE.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -119,7 +119,7 @@
|
|||||||
<li><a href="#mechanik">Mechanik-Erkenntnisse aus Trace-Tests</a></li>
|
<li><a href="#mechanik">Mechanik-Erkenntnisse aus Trace-Tests</a></li>
|
||||||
<li><a href="#ui">UI / Interaktion</a></li>
|
<li><a href="#ui">UI / Interaktion</a></li>
|
||||||
<li><a href="#visual">Visualisierung (2D / 3D)</a></li>
|
<li><a href="#visual">Visualisierung (2D / 3D)</a></li>
|
||||||
<li><a href="#erklaerungen">Erklärungen für Kinder 10–14</a></li>
|
<li><a href="#erklaerungen">Erklärungen für Kinder 10 – 14</a></li>
|
||||||
<li><a href="#folgen">Weitere Folgen des Klimawandels</a></li>
|
<li><a href="#folgen">Weitere Folgen des Klimawandels</a></li>
|
||||||
<li><a href="#offen">Offene Punkte & nächste Schritte</a></li>
|
<li><a href="#offen">Offene Punkte & nächste Schritte</a></li>
|
||||||
</ol>
|
</ol>
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
<section id="ziel">
|
<section id="ziel">
|
||||||
<h2>1. Didaktisches Ziel und Zielgruppe</h2>
|
<h2>1. Didaktisches Ziel und Zielgruppe</h2>
|
||||||
|
|
||||||
<p>Der <strong>Klimawächter</strong> ist kein Spiel um einen high score. Er soll Schülerinnen und Schülern der 1.–4. Klasse Sekundarstufe I (ca. <strong>10–14 Jahre</strong>) vermitteln, dass:</p>
|
<p>Der <strong>Klimawächter</strong> ist kein Spiel um einen high score. Er soll Schülerinnen und Schülern der 1.–4. Klasse Sekundarstufe I (ca. <strong>10 – 14 Jahre</strong>) vermitteln, dass:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>der Klimawandel träge ist — Folgen kommen verzögert, Entscheidungen wirken erst in Jahren</li>
|
<li>der Klimawandel träge ist — Folgen kommen verzögert, Entscheidungen wirken erst in Jahren</li>
|
||||||
<li>es keine Wunderlösung gibt — man muss mehrere Strategien kombinieren</li>
|
<li>es keine Wunderlösung gibt — man muss mehrere Strategien kombinieren</li>
|
||||||
@@ -202,7 +202,7 @@
|
|||||||
<li>Deiche allein retten kurzfristig Häuser, aber nicht das Klima.</li>
|
<li>Deiche allein retten kurzfristig Häuser, aber nicht das Klima.</li>
|
||||||
<li>Aufforsten ist wichtig, aber als Einzelmaßnahme zu langsam.</li>
|
<li>Aufforsten ist wichtig, aber als Einzelmaßnahme zu langsam.</li>
|
||||||
<li>Wind ist stärker als Solar, aber wegen Wartungskosten nicht immer die beste Wahl.</li>
|
<li>Wind ist stärker als Solar, aber wegen Wartungskosten nicht immer die beste Wahl.</li>
|
||||||
<li>Das Klima hat eine Trägheit von ca. 8 %/Jahr für Temperatur und 6–8 %/Jahr für Meeresspiegel.</li>
|
<li>Das Klima hat eine Trägheit von ca. 8 %/Jahr für Temperatur und 6 – 8 %/Jahr für Meeresspiegel.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -226,7 +226,7 @@
|
|||||||
|
|
||||||
<div class="card green">
|
<div class="card green">
|
||||||
<h3><span class="tag done">done</span> Bürger-Beschwerden mit Choices</h3>
|
<h3><span class="tag done">done</span> Bürger-Beschwerden mit Choices</h3>
|
||||||
<p>Modaler Dialog mit Avatar, Message und 2–3 Choice-Karten. Jede Wahl hat echte Konsequenzen. Spiel pausiert automatisch bis der Spieler entscheidet. Bibliothek: 7 Events über die 75 Jahre verteilt (Fischerin, Forscherin, Bauer, Hotel, Jugend-Streik, Industrie, Bergdorf).</p>
|
<p>Modaler Dialog mit Avatar, Message und 2 – 3 Choice-Karten. Jede Wahl hat echte Konsequenzen. Spiel pausiert automatisch bis der Spieler entscheidet. Bibliothek: 7 Events über die 75 Jahre verteilt (Fischerin, Forscherin, Bauer, Hotel, Jugend-Streik, Industrie, Bergdorf).</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card green">
|
<div class="card green">
|
||||||
@@ -336,7 +336,7 @@
|
|||||||
|
|
||||||
<div class="card sand">
|
<div class="card sand">
|
||||||
<h3><span class="tag todo">todo</span> Krisen-Mini-Spiele</h3>
|
<h3><span class="tag todo">todo</span> Krisen-Mini-Spiele</h3>
|
||||||
<p>Bei Sturmflut, Hitzewelle, Murenabgang ein kurzes (10–20 s) Klick-Mini-Spiel. Erfolg mildert den Schaden.</p>
|
<p>Bei Sturmflut, Hitzewelle, Murenabgang ein kurzes (10 – 20 s) Klick-Mini-Spiel. Erfolg mildert den Schaden.</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -394,9 +394,9 @@
|
|||||||
|
|
||||||
<!-- ====================================================== -->
|
<!-- ====================================================== -->
|
||||||
<section id="erklaerungen">
|
<section id="erklaerungen">
|
||||||
<h2>6. Erklärungen für Kinder 10–14</h2>
|
<h2>6. Erklärungen für Kinder 10 – 14</h2>
|
||||||
|
|
||||||
<p>Das neue <code>info-overlay.ts</code>-Modul bietet eine Bibliothek kindgerechter Erklärtexte. Jeder Graph und jedes wichtige Event hat einen (i)-Button, der ein Overlay öffnet. Zielgruppe: 10–14 Jahre, einfache Sätze, max. 3 Absätze plus "Merksatz".</p>
|
<p>Das neue <code>info-overlay.ts</code>-Modul bietet eine Bibliothek kindgerechter Erklärtexte. Jeder Graph und jedes wichtige Event hat einen (i)-Button, der ein Overlay öffnet. Zielgruppe: 10 – 14 Jahre, einfache Sätze, max. 3 Absätze plus "Merksatz".</p>
|
||||||
|
|
||||||
<h3>Bisher erklärte Begriffe</h3>
|
<h3>Bisher erklärte Begriffe</h3>
|
||||||
<ul>
|
<ul>
|
||||||
|
|||||||
@@ -491,7 +491,7 @@
|
|||||||
<h2>Was den Alltag schwer macht</h2>
|
<h2>Was den Alltag schwer macht</h2>
|
||||||
<h3>Stundenknappheit & Stofffülle</h3>
|
<h3>Stundenknappheit & Stofffülle</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>1–2 Wo.-Std. — der ganze Lehrplan muss durchgenommen werden</li>
|
<li>1 – 2 Wo.-Std. — der ganze Lehrplan muss durchgenommen werden</li>
|
||||||
<li>Tendenz zur Oberflächlichkeit, Verlust von Tiefe</li>
|
<li>Tendenz zur Oberflächlichkeit, Verlust von Tiefe</li>
|
||||||
</ul>
|
</ul>
|
||||||
<h3>Lehrkräftebildung</h3>
|
<h3>Lehrkräftebildung</h3>
|
||||||
|
|||||||
@@ -428,7 +428,7 @@
|
|||||||
<li><strong>Lernende ↔ Lehrperson:</strong> Reflexion, Bedeutungsaushandlung, soziales Lernen</li>
|
<li><strong>Lernende ↔ Lehrperson:</strong> Reflexion, Bedeutungsaushandlung, soziales Lernen</li>
|
||||||
<li><strong>Lehrperson ↔ Simulation:</strong> Auswahl, Vorbereitung, Didaktisierung — Inszenierung des Lernanlasses</li>
|
<li><strong>Lehrperson ↔ Simulation:</strong> Auswahl, Vorbereitung, Didaktisierung — Inszenierung des Lernanlasses</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>Studienbefund: Bei kompetenter Begleitung steigt die Effektstärke um den Faktor 1,5–2.</p>
|
<p>Studienbefund: Bei kompetenter Begleitung steigt die Effektstärke um den Faktor 1,5 – 2.</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- KAPITEL 6 -->
|
<!-- KAPITEL 6 -->
|
||||||
|
|||||||