diff --git a/App/admin-lehrer-view.html b/App/admin-lehrer-view.html
index 32ea8da..c29b93e 100644
--- a/App/admin-lehrer-view.html
+++ b/App/admin-lehrer-view.html
@@ -97,7 +97,7 @@
Der Zeilen-Prozentwert in „Ergebnisse/Aktuell" entsteht in live.php · extractScore: liefert die Sim einen eigenen 0–100-Score, wird der genommen; sonst aus den Sternen: % = Sterne / 5 × 100.
Die Sterne berechnet jede App am Ende selbst — aus unterschiedlichen Kennzahlen und Schwellen (je Sim unten dokumentiert). Deshalb bedeuten 4★ heute nicht überall dasselbe.
Uneinheitlichkeiten: Busfahrt · Fluggesellschaft · Kofferdetektiv liefern nur 0–3 Sterne statt 0–5. Busfahrt/Fluggesellschaft senden als score rohe Punkte (nicht 0–100 → Zeilen-% verzerrt). Weltküche wertet pro Gericht statt pro Durchgang. Logistik überträgt weder Sterne noch Score. Ein echter 0–100-Live-Score existiert nur bei Klima, Fluss, Heli, Kofferdetektiv, Tourismusregion (abgeleitet Tourismustal).
- Vorschlag (ein System): Jede App liefert genau eine Funktion → Leistungs-% (0–100) als Wahrheit (grün→rot-Balken). Sterne werden daraus mit denselben globalen Schwellen für alle abgeleitet (z. B. ≥90→5★ · ≥75→4 · ≥55→3 · ≥35→2 · sonst 1). Dann bedeuten Sterne überall dasselbe und % ↔ Sterne sind konsistent.
+ Beschlossen: Sterne entfallen. Jede App liefert genau eine Funktion → Leistungs-% (0–100) als Wahrheit, überall gezeigt als Prozent + Balken in der 5-stufigen Farbskala. Auch Fördern/Begleiten/Läuft gut leiten sich aus diesem % ab.
@@ -539,21 +539,23 @@ var PROPOSED_PCT = {
};
// 5-stufige Farbskala (= _tagTraffic).
-function pctColor(v){ v=Math.max(0,Math.min(100,v||0)); return v>=80?'#1f7a3d':v>=65?'#7cb342':v>=50?'#f9c33b':v>=35?'#ef8b2c':'#d8453a'; }
-// Standard-Anzeige überall: Prozent in Ampelfarbe + dünner Balken darunter.
+// ≥90 Dunkelgrün · ≥80 Hellgrün · ≥60 Gelb · ≥50 Orange · <50 Rot
+function pctColor(v){ v=Math.max(0,Math.min(100,v||0)); return v>=90?'#1f7a3d':v>=80?'#7cb342':v>=60?'#f9c33b':v>=50?'#ef8b2c':'#d8453a'; }
+// Standard-Anzeige überall: Prozent in Ampelfarbe + schmaler Balken darunter (kompakt).
function pctBar(pct){
if (pct==null) return '—';
var c=pctColor(pct);
- return ''
- + '
'+pct+' %
'
- + '
'
+ return '
'
+ + '
'+pct+' %
'
+ + '
'
+ '
';
}
// Triage aus Leistungs-% × Aktivität — Grenzen = Farbgrenzen, damit Feld ↔ Farbe stimmen.
+// <50 (Rot) → Fördern · 50–79 (Orange+Gelb) → Begleiten · ≥80 (Grün) → Läuft gut.
function triage(pct, active){
if (pct==null || !active) return {lab:'inaktiv', col:'#8a8a8a', bg:'#efeee8'};
if (pct<50) return {lab:'Fördern', col:'#b04a3e', bg:'#f7e2dc'};
- if (pct<65) return {lab:'Begleiten', col:'#9a6a12', bg:'#f7edd6'};
+ if (pct<80) return {lab:'Begleiten', col:'#9a6a12', bg:'#f7edd6'};
return {lab:'Läuft gut', col:'#3a6b3e', bg:'#dceadd'};
}
function triageChip(pct, active){ var t=triage(pct,active); return '
'+t.lab+''; }
@@ -780,20 +782,20 @@ function renderSim(meta){
// Vorschau-Karte: neues Bewertungssystem (Skala · %+Balken · Triage)
function renderPreviewCard(){
- var samples=[92,71,55,40,22];
+ var samples=[95,84,68,54,30];
var h='
';
h+='
🎯 Neues Bewertungssystem — Vorschau zum Abnehmen
';
h+='
Standard überall: ein Leistungs-% (0–100) in der Ampelfarbe + dünner Balken darunter — Sterne fallen weg. Beispiele:
';
h+='
'+samples.map(function(v){return pctBar(v);}).join('')+'
';
h+='
5-stufige Farbskala (= bestehende Tag-Skala):
';
h+='
'
- +[['≥ 80','sehr gut',92],['65–79','gut',71],['50–64','solide',55],['35–49','wackelig',40],['< 35','kritisch',22]]
+ +[['≥ 90','sehr gut',95],['80–89','gut',84],['60–79','solide',68],['50–59','wackelig',54],['< 50','kritisch',30]]
.map(function(b){var c=pctColor(b[2]);return ''+b[0]+' % · '+b[1]+'';}).join('')+'
';
h+='
Triage-Felder aus %×Fleiß — die Grenzen sind die Farbgrenzen, damit Balkenfarbe und Feld immer übereinstimmen:
';
h+='
'
+[22,55,80].map(function(v){return '
'+pctBar(v)+'
'+triageChip(v,true)+'
';}).join('')
+'
'+pctBar(null)+'
'+triageChip(null,false)+'
'+'
';
- h+='
< 50 % → Fördern · 50–64 % → Begleiten · ≥ 65 % → Läuft gut — jeweils nur, wenn aktiv gearbeitet wurde; sonst „inaktiv".
';
+ h+='
< 50 % → Fördern · 50–79 % → Begleiten · ≥ 80 % → Läuft gut — jeweils nur, wenn aktiv gearbeitet wurde; sonst „inaktiv".
';
h+='
Unten hat jede Tabelle die neue Spalte 🎯 Leistung — die Vorschlags-Formel je Sim, auf die Testdaten gerechnet. Bei Busfahrt · Fluggesellschaft · Sonnensystem ist der Wert eine Näherung (Topo-/Erstversuch-Rohwerte fehlen in den Testdaten).
';
h+='
';
return h;
diff --git a/App/admin-styleguide.html b/App/admin-styleguide.html
index 56c6e03..980b115 100644
--- a/App/admin-styleguide.html
+++ b/App/admin-styleguide.html
@@ -64,6 +64,7 @@
🎨 Bildstil
🌿 Bild-Farbpalette
🧩 UI-Farbtokens
+
📊 Leistungs-Skala
📐 Layout & Spacing
🔤 Typografie
🖼️ Bildformate
@@ -195,6 +196,50 @@
+
+
+
Leistungs-Skala — 5 Stufen (Standard für ALLE Sims)
+
EINE Skala für Schülerleistung überall im Programm: Prozentwert (0–100) + dünner Balken in der Ampelfarbe. Sterne entfallen. Die Grenzen 50 / 65 sind zugleich die Triage-Grenzen, damit Balkenfarbe und Feld (Fördern/Begleiten/Läuft gut) immer übereinstimmen. Quelle in Code: pctColor() = _tagTraffic().
+
+
+
+
+
50–59 % · wackelig#ef8b2c
+
+
+
+
Standard-Anzeige: Prozent + Balken
+
+
+
Triage-Felder (aus % × Fleiß)
+
< 50 % → Fördern · 50–79 % → Begleiten · ≥ 80 % → Läuft gut — jeweils nur, wenn aktiv gearbeitet wurde, sonst „inaktiv".
+
+ Fördern
+ Begleiten
+ Läuft gut
+ inaktiv
+
+
+
CSS-Variablen
+
--ggs-perf-5: #1f7a3d; /* ≥ 90 % sehr gut */
+--ggs-perf-4: #7cb342; /* 80–89 % gut */
+--ggs-perf-3: #f9c33b; /* 60–79 % solide */
+--ggs-perf-2: #ef8b2c; /* 50–59 % wackelig */
+--ggs-perf-1: #d8453a; /* < 50 % kritisch */
+/* Triage */
+--ggs-triage-foerdern: #b04a3e;
+--ggs-triage-begleiten: #9a6a12;
+--ggs-triage-gut: #3a6b3e;
+--ggs-triage-inaktiv: #8a8a8a;
+
Vorschau der Zuordnung je Sim: Lehrer-View Parameter-Überblick.
+
+
Layout & Spacing
diff --git a/App/teacher.html b/App/teacher.html
index 5a5ce88..872a1af 100644
--- a/App/teacher.html
+++ b/App/teacher.html
@@ -624,14 +624,7 @@ function _liveBar(val, min, max, goodWhen, dec) {
if (val == null || isNaN(val)) return '
—';
var span = (max - min) || 1;
var pct = Math.max(0, Math.min(100, ((val - min) / span) * 100));
- var c;
- if (goodWhen === 'low') {
- c = pct <= 33 ? '#5a8a5e' : pct <= 66 ? '#c9913a' : '#c07a6b';
- } else if (goodWhen === 'mid') {
- c = '#7a7a7a';
- } else {
- c = pct >= 60 ? '#5a8a5e' : pct >= 30 ? '#c9913a' : '#c07a6b';
- }
+ var c = _liveBarColor(pct, goodWhen);
var disp = (dec != null) ? Number(val).toFixed(dec) : Math.round(val);
return ''
+ '
'
@@ -645,9 +638,9 @@ function _liveBar(val, min, max, goodWhen, dec) {
// Farbwahl nach „gut wenn" (aus _liveBar extrahiert) — für die Mini-Balken.
function _liveBarColor(pct, goodWhen) {
pct = Math.max(0, Math.min(100, pct||0));
- if (goodWhen === 'low') return pct <= 33 ? '#5a8a5e' : pct <= 66 ? '#c9913a' : '#c07a6b';
if (goodWhen === 'mid') return '#9a9a9a';
- return pct >= 60 ? '#5a8a5e' : pct >= 30 ? '#c9913a' : '#c07a6b';
+ // EINE Skala (_pctColor). „Niedrig ist gut" invertiert, damit Grün weiter „gut" heißt.
+ return _pctColor(goodWhen === 'low' ? 100 - pct : pct);
}
// Mini-Balken UNTER der Zahl (Cockpit „Aktuell"). pct 0 – 100.
function _liveMiniBar(pct, color) {
@@ -656,6 +649,58 @@ function _liveMiniBar(pct, color) {
+ ' ';
}
+// ===== EINHEITLICHE LEISTUNGS-SKALA (Styleguide „Leistungs-Skala", 5 Stufen) =====
+// EINE Farbfunktion für Schülerleistung überall. Grenzen 50/65 = Triage-Grenzen.
+function _pctColor(v) {
+ v = Math.max(0, Math.min(100, v || 0));
+ // ≥90 Dunkelgrün · ≥80 Hellgrün · ≥60 Gelb · ≥50 Orange · <50 Rot
+ return v >= 90 ? '#1f7a3d' : v >= 80 ? '#7cb342' : v >= 60 ? '#f9c33b' : v >= 50 ? '#ef8b2c' : '#d8453a';
+}
+// Standard-Anzeige: Prozent in Ampelfarbe + dünner Balken darunter.
+function _pctBar(pct) {
+ if (pct == null || isNaN(pct)) return '—';
+ pct = Math.max(0, Math.min(100, Math.round(pct)));
+ var c = _pctColor(pct);
+ // Kompakt: schmaler Balken (max ~34px), damit die Spalte wenig Platz braucht.
+ return ''
+ + '
'+pct+' %
'
+ + '
'
+ + '
';
+}
+// Das EINE Leistungs-% (0–100) je Sim aus dem Live-State. Sims mit eigenem
+// 0–100-Score nutzen ihn direkt; sonst aus den vorhandenen Kennzahlen abgeleitet.
+function _leistungPct(simId, s) {
+ if (!s || typeof s !== 'object') return null;
+ var cl = function(v,a,b){ return Math.max(a, Math.min(b, v)); };
+ // Sims mit echtem 0–100-Live-Score (klima, fluss, kofferdetektiv, heli) direkt:
+ if (typeof s.score === 'number' && simId !== 'weltkueche' && simId !== 'busfahrt' && simId !== 'fluggesellschaft')
+ return cl(Math.round(s.score), 0, 100);
+ switch (simId) {
+ case 'klima': case 'klima-3d': {
+ var sp = (s.level >= 3) ? 7000 : 6000;
+ return Math.round(cl(35-((s.temp||16)-15.5)*20,0,35) + Math.max(0,25*(1-(s.floodedPct||0)/50)) + cl((s.budget||0)/10,0,20) + cl((s.population||0)/sp*20,0,20));
+ }
+ case 'fluss': return (s.biodiv!=null) ? cl(Math.round((s.biodiv+s.economy+s.flood)/3) - (s.settlementsLost||0)*5, 0, 100) : null;
+ case 'heli': return (s.totalStars!=null) ? Math.round(s.totalStars/5*100) : (s.planTotal ? Math.round((s.planCorrect||0)/s.planTotal*100) : null);
+ case 'farmer': {
+ if (s.hit_rate==null && s.cash==null) return null;
+ var money=cl(50+(s.cash||0)/300,0,100), co2=cl(100-(s.co2_kg||0)/80,0,100), dis=cl(100-(s.diseases||0)*20,0,100), crop=cl((s.crops_used||0)/10*100,0,100), country=cl((s.countries||0)/6*100,0,100);
+ return Math.round(0.25*money + 0.20*(s.hit_rate||0) + 0.15*co2 + 0.10*dis + 0.10*crop + 0.10*country + 0.10*70);
+ }
+ case 'energiemanager': { var d=(s.day||1)*(s.blockTotal||8); return cl(Math.round((s.setBlocksBalanced||0)/d*100) - 15*(s.blackouts||0), 0, 100); }
+ case 'eu-werkstatt': return s.stepsTotal ? cl(Math.round((s.firstTryHits||0)/s.stepsTotal*100) - 5*(s.muelleimerCount||0), 0, 100) : null;
+ case 'weltkueche': { var t=(s.correctClicks||0)+(s.wrongClicks||0); return t ? Math.round((s.correctClicks||0)/t*100) : null; }
+ case 'busfahrt': case 'fluggesellschaft':
+ return s.topoMax ? cl(Math.round((s.topoPoints||0)/s.topoMax*100),0,100)
+ : (s.totalOrders ? Math.round((s.wonOrders||0)/s.totalOrders*100) : null);
+ case 'sonnensystem': return (s.progressPct!=null) ? cl(Math.round(s.progressPct),0,100) : (s.taskTotal ? Math.round((s.completed||0)/s.taskTotal*100) : null);
+ case 'tourismustal': return s.goalsTotal ? Math.round(0.5*((s.goalsDone||0)/s.goalsTotal*100) + 0.3*(s.satisfaction||0) + 0.2*(s.nature||0)) : null;
+ case 'tourismusregion': return (s.avgStars!=null) ? Math.round(0.6*(s.avgStars/5*100) + 0.4*(s.env||0)) : null;
+ case 'logistik': return (s.balance!=null) ? cl(Math.round(s.balance/16000*100),0,100) : null;
+ }
+ return (typeof s.score === 'number') ? cl(Math.round(s.score),0,100) : null;
+}
+
var LIVE_PRIMARY_FIELDS = {
farmer: [
{k:'year', label:'Jahr', bar:{kind:'abs',max:10,good:'mid'}, fmt:function(v){ return (v||0) + ' / 10'; }},
@@ -1314,6 +1359,10 @@ function _liveProgressPct(simId, s) {
}
function _liveMood(simId, s) {
if (!s) return null;
+ // Einheitliche Skala: Leistungs-% steuert die Stimmung (≥80 gut · ≥50 mittel · <50 schlecht),
+ // deckungsgleich mit Farbe + Triage (Läuft gut / Begleiten / Fördern).
+ var _p = _leistungPct(simId, s);
+ if (_p != null) return _p >= 80 ? 'good' : _p >= 50 ? 'mid' : 'bad';
if (s.health) return ({good:'good',ok:'good',warning:'mid',bad:'bad',critical:'bad'})[s.health] || 'mid';
if (_liveNeedsHelp(simId, s)) return 'bad';
switch (simId) {
@@ -1548,6 +1597,7 @@ async function renderLive() {
html += '';
html += '| Schüler*in | ';
html += 'Seit | ';
+ html += '🎯 Leistung | ';
fields.forEach(function(f) {
var align = f.align || 'right';
html += ''+esc(f.label)+(f.unit?' '+f.unit+'':'')+' | ';
@@ -1596,6 +1646,7 @@ async function renderLive() {
html += '
';
html += '| '+(help?'🆘':(paused?'⏸':''))+avatarHtml+''+esc(ls.displayName)+' | ';
html += ''+missionStr+' '+simStr+' ges. | ';
+ html += ''+_pctBar(_leistungPct(ls.module_id, s))+' | ';
fields.forEach(function(f) {
var align = f.align || 'right';
var cell = _liveFmt(s[f.k], f.unit, f, ls);
@@ -1705,10 +1756,7 @@ function _liveSuccessColor(success) {
})[success] || '#7a8a9c';
}
// Ampelfarbe für den Tages-Erfolg (0–100): grün → hellgrün → gelb → orange → rot.
-function _tagTraffic(v) {
- v = Math.max(0, Math.min(100, v || 0));
- return v >= 80 ? '#1f7a3d' : v >= 65 ? '#7cb342' : v >= 50 ? '#f9c33b' : v >= 35 ? '#ef8b2c' : '#d8453a';
-}
+function _tagTraffic(v) { return _pctColor(v); } // EINE Skala (siehe _pctColor)
function _liveSuccessIcon(stars, success) {
if (success === 'aborted') return '✗';
if (success === 'running') return '⏳';