Abzeichen: thematische Sim-Abzeichen + saubere Icons + Lehreransicht
- badges.js: 5 Basis-Abzeichen + je Sim 1 thematisches (Vier-Jahreszeiten, Sterne-Koch, Alpen-Pilot …), 19 sauber generierte Flat-Emblem-Icons (keine Emojis) - Erfolge-Tab: Sammlung mit Bild-Icons, gesperrte grau = Ziele; alte Emoji-Badges raus - Lehrer-Matrix: neue Spalte „Abzeichen" (freigeschaltet von gesamt) pro Schüler:in Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
@@ -1,44 +1,63 @@
|
||||
/* ============================================================================
|
||||
* GeoGraSim — gemeinsame Abzeichen-Bibliothek
|
||||
* ----------------------------------------------------------------------------
|
||||
* Ein einheitliches Set von 5 Abzeichen pro Simulation, dessen Freischalt-
|
||||
* Bedingungen sich aus den ALLGEMEINEN Leistungsdaten (plays, best_stars,
|
||||
* level) berechnen. Dadurch gelten die Abzeichen SOFORT für JEDE Sim — auch
|
||||
* für die ohne eigenen Achievement-Code — und sind über alle Sims vergleichbar.
|
||||
* Zwei Ebenen, für alle Sims einheitlich strukturiert:
|
||||
* 1) BASIS — 5 Abzeichen, in jeder Sim identisch, Bedingung aus allgemeinen
|
||||
* Leistungsdaten (plays / best_stars / level) → gelten sofort für JEDE Sim.
|
||||
* 2) THEMATISCH — je Sim ein eigenes, thematisch benanntes Abzeichen.
|
||||
*
|
||||
* Abzeichen sind bewusst auch im GESPERRTEN Zustand sichtbar (Zielvorstellung),
|
||||
* die Beschreibung ist zugleich das Ziel. Sterne laufen parallel (Qualität pro
|
||||
* Durchlauf); ggsErfolgToStars() bildet den normierten Erfolg (0–100) auf
|
||||
* 0–5 Sterne ab.
|
||||
* Icons sind sauber generierte Emblems (Flat-Scandinavian, keine Emojis) unter
|
||||
* assets/img/badges/. Gesperrte Abzeichen bleiben sichtbar (Zielvorstellung),
|
||||
* die Beschreibung ist das Ziel. Sterne laufen parallel; ggsErfolgToStars()
|
||||
* bildet den normierten Erfolg (0–100) auf 0–5 Sterne ab.
|
||||
*
|
||||
* Verwendung (Client):
|
||||
* GGS_BADGES.forEach(b => { var earned = b.cond(progressObj); ... });
|
||||
* progressObj = { plays, best_stars, level } (pro Sim)
|
||||
* progressObj (pro Sim): { plays, best_stars, level }
|
||||
* ==========================================================================*/
|
||||
(function (root) {
|
||||
'use strict';
|
||||
var IMG = 'assets/img/badges/';
|
||||
|
||||
// Fünf Abzeichen-Typen, für alle Sims identisch. cond(p) wird pro Sim geprüft.
|
||||
// 1) BASIS — 5 Abzeichen, überall gleich.
|
||||
var GGS_BADGES = [
|
||||
{ id: 'einstieg', icon: '🌱', name: 'Einstieg',
|
||||
desc: 'Ersten Durchlauf geschafft',
|
||||
cond: function (p) { return (p && p.plays || 0) >= 1; } },
|
||||
{ id: 'fortgeschritten', icon: '📈', name: 'Fortgeschritten',
|
||||
desc: 'Level 2 erreicht',
|
||||
cond: function (p) { return (p && p.level || 1) >= 2; } },
|
||||
{ id: 'koenner', icon: '⭐', name: 'Könner',
|
||||
desc: 'Vier Sterne geschafft',
|
||||
cond: function (p) { return (p && p.best_stars || 0) >= 4; } },
|
||||
{ id: 'meister', icon: '🏆', name: 'Meister',
|
||||
desc: 'Fünf Sterne — Bestleistung',
|
||||
cond: function (p) { return (p && p.best_stars || 0) >= 5; } },
|
||||
{ id: 'ausdauer', icon: '🔥', name: 'Ausdauer',
|
||||
desc: 'Acht Durchläufe',
|
||||
cond: function (p) { return (p && p.plays || 0) >= 8; } }
|
||||
{ id: 'einstieg', icon: IMG + 'bg-einstieg.webp', name: 'Einstieg',
|
||||
desc: 'Ersten Durchlauf geschafft', cond: function (p) { return (p && p.plays || 0) >= 1; } },
|
||||
{ id: 'fortgeschritten', icon: IMG + 'bg-fortgeschritten.webp', name: 'Fortgeschritten',
|
||||
desc: 'Level 2 erreicht', cond: function (p) { return (p && p.level || 1) >= 2; } },
|
||||
{ id: 'koenner', icon: IMG + 'bg-koenner.webp', name: 'Könner',
|
||||
desc: 'Vier Sterne geschafft', cond: function (p) { return (p && p.best_stars || 0) >= 4; } },
|
||||
{ id: 'meister', icon: IMG + 'bg-meister.webp', name: 'Meister',
|
||||
desc: 'Fünf Sterne — Bestleistung', cond: function (p) { return (p && p.best_stars || 0) >= 5; } },
|
||||
{ id: 'ausdauer', icon: IMG + 'bg-ausdauer.webp', name: 'Ausdauer',
|
||||
desc: 'Acht Durchläufe', cond: function (p) { return (p && p.plays || 0) >= 8; } }
|
||||
];
|
||||
|
||||
// Bedingungs-Bausteine für thematische Abzeichen
|
||||
function lvl3(p) { return (p && p.level || 1) >= 3; }
|
||||
function star5(p) { return (p && p.best_stars || 0) >= 5; }
|
||||
function played6(p) { return (p && p.plays || 0) >= 6; }
|
||||
function b(sim, name, desc, cond) {
|
||||
return { id: 'th-' + sim, icon: IMG + 'th-' + sim + '.webp', name: name, desc: desc, cond: cond, thematic: true };
|
||||
}
|
||||
|
||||
// 2) THEMATISCH — je Sim ein eigenes Abzeichen (Name/Icon sim-spezifisch).
|
||||
var GGS_SIM_BADGES = {
|
||||
'tourismustal': b('tourismustal', 'Vier-Jahreszeiten', 'Level 3 im Tourismustal', lvl3),
|
||||
'tourismusregion': b('tourismusregion', 'Regionen-Meister', 'Fünf Sterne in der Region', star5),
|
||||
'kofferdetektiv': b('kofferdetektiv', 'Welt-Detektiv', 'Sechs Fälle gelöst', played6),
|
||||
'busfahrt': b('busfahrt', 'Europa-Kenner', 'Fünf Sterne auf der Tour', star5),
|
||||
'logistik': b('logistik', 'Lieferketten-Profi','Level 3 in der Logistik', lvl3),
|
||||
'weltkueche': b('weltkueche', 'Sterne-Koch', 'Fünf Sterne in der Weltküche', star5),
|
||||
'farmer': b('farmer', 'Ernte-Meister', 'Sechs Ernten eingefahren', played6),
|
||||
'klima': b('klima', '1,5-Grad-Held', 'Fünf Sterne im Klimaschutz', star5),
|
||||
'klima-3d': b('klima-3d', 'Küstenwächter', 'Level 3 im Küstenschutz', lvl3),
|
||||
'fluss': b('fluss', 'Renaturierer', 'Fünf Sterne am Fluss', star5),
|
||||
'energiemanager': b('energiemanager', 'Grundlast-Profi', 'Level 3 im Energienetz', lvl3),
|
||||
'heli': b('heli', 'Alpen-Pilot', 'Sechs Einsätze geflogen', played6),
|
||||
'sonnensystem': b('sonnensystem', 'Astronom', 'Fünf Sterne am Himmel', star5),
|
||||
'eu-werkstatt': b('eu-werkstatt', 'Gesetzgeber:in', 'Level 3 in der EU-Werkstatt', lvl3)
|
||||
};
|
||||
|
||||
// Spielbare Simulationen (aktiv/beta) — Reihenfolge wie im Schulbuch.
|
||||
// Auch ungespielte erscheinen in der Sammlung: alle 5 Abzeichen gesperrt = Ziel.
|
||||
var GGS_SIMS = [
|
||||
{ id: 'tourismustal', title: 'Tourismustal', icon: '🏔️' },
|
||||
{ id: 'tourismusregion', title: 'Tourismusregion', icon: '🗺️' },
|
||||
@@ -56,21 +75,26 @@
|
||||
{ id: 'eu-werkstatt', title: 'EU-Werkstatt', icon: '🏛️' }
|
||||
];
|
||||
|
||||
// Normierter Erfolg (0–100) → 0–5 Sterne. Ein Rechenweg für alle Sims.
|
||||
// Alle Abzeichen einer Sim = 5 Basis + (falls vorhanden) 1 thematisches.
|
||||
function ggsSimBadges(simId) {
|
||||
var out = GGS_BADGES.slice();
|
||||
if (GGS_SIM_BADGES[simId]) out.push(GGS_SIM_BADGES[simId]);
|
||||
return out;
|
||||
}
|
||||
function ggsBadgeCount(p, simId) {
|
||||
var list = simId ? ggsSimBadges(simId) : GGS_BADGES, n = 0;
|
||||
for (var i = 0; i < list.length; i++) if (list[i].cond(p)) n++;
|
||||
return n;
|
||||
}
|
||||
function ggsErfolgToStars(pct) {
|
||||
if (pct == null || isNaN(pct)) return 0;
|
||||
return Math.max(0, Math.min(5, Math.round(pct / 20)));
|
||||
}
|
||||
|
||||
// Wie viele der 5 Abzeichen sind für ein Progress-Objekt freigeschaltet?
|
||||
function ggsBadgeCount(p) {
|
||||
var n = 0;
|
||||
for (var i = 0; i < GGS_BADGES.length; i++) if (GGS_BADGES[i].cond(p)) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
root.GGS_BADGES = GGS_BADGES;
|
||||
root.GGS_SIMS = GGS_SIMS;
|
||||
root.ggsErfolgToStars = ggsErfolgToStars;
|
||||
root.ggsBadgeCount = ggsBadgeCount;
|
||||
root.GGS_BADGES = GGS_BADGES;
|
||||
root.GGS_SIM_BADGES = GGS_SIM_BADGES;
|
||||
root.GGS_SIMS = GGS_SIMS;
|
||||
root.ggsSimBadges = ggsSimBadges;
|
||||
root.ggsBadgeCount = ggsBadgeCount;
|
||||
root.ggsErfolgToStars = ggsErfolgToStars;
|
||||
})(typeof window !== 'undefined' ? window : this);
|
||||
|
||||
@@ -53,6 +53,8 @@
|
||||
.badge-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:.6rem}
|
||||
.badge{background:#fff;border-radius:10px;padding:.8rem;text-align:center;border:1.5px solid rgba(0,0,0,.04)}
|
||||
.badge .icon{font-size:2rem;margin-bottom:.3rem}
|
||||
.badge .icon img{width:48px;height:48px;object-fit:contain;display:block;margin:0 auto}
|
||||
.badge.locked-b .icon img{filter:grayscale(.9)}
|
||||
.badge .name{font-size:.72rem;font-weight:700}
|
||||
.badge .desc{font-size:.6rem;color:#8a8a8a}
|
||||
.badge.earned{border-color:#4a7c8a;background:linear-gradient(135deg,#f0f8fa,#fff)}
|
||||
@@ -359,19 +361,6 @@ async function renderAchievements() {
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
// Auszeichnungen / Badges
|
||||
html += '<p style="font-size:.85rem;font-weight:700;color:#4a7c8a;margin-bottom:.5rem">Auszeichnungen</p>';
|
||||
html += '<div class="badge-grid">';
|
||||
BADGES.forEach(function(b) {
|
||||
var isEarned = earned.indexOf(b.id) !== -1;
|
||||
html += '<div class="badge '+(isEarned ? 'earned' : 'locked-b')+'">';
|
||||
html += '<div class="icon">'+b.icon+'</div>';
|
||||
html += '<div class="name">'+b.name+'</div>';
|
||||
html += '<div class="desc">'+b.desc+'</div>';
|
||||
html += '</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
|
||||
// === Abzeichen-Sammlung pro Simulation (gesperrte = sichtbare Ziele) ===
|
||||
if (typeof GGS_SIMS !== 'undefined') {
|
||||
var progBySim = {};
|
||||
@@ -380,18 +369,20 @@ async function renderAchievements() {
|
||||
html += '<p style="font-size:.68rem;color:#8a8a8a;margin:0 0 .7rem">Jede Simulation hat fünf Abzeichen. Blasse sind noch nicht geschafft — das sind deine Ziele.</p>';
|
||||
GGS_SIMS.forEach(function(sim){
|
||||
var p = progBySim[sim.id] || {plays:0, best_stars:0, level:1};
|
||||
var got = ggsBadgeCount(p);
|
||||
var list = ggsSimBadges(sim.id);
|
||||
var got = ggsBadgeCount(p, sim.id);
|
||||
var st = (p.best_stars||0);
|
||||
var stars = '★'.repeat(st) + '☆'.repeat(Math.max(0, 5-st));
|
||||
html += '<div class="sim-coll"><div class="sim-coll-head">'
|
||||
+ '<span class="sc-ic">'+sim.icon+'</span><span class="sc-t">'+sim.title+'</span>'
|
||||
+ '<span class="sc-stars" title="Beste Sterne">'+stars+'</span>'
|
||||
+ '<span class="sc-count">'+got+' / '+GGS_BADGES.length+'</span></div>';
|
||||
+ '<span class="sc-count">'+got+' / '+list.length+'</span></div>';
|
||||
html += '<div class="badge-grid">';
|
||||
GGS_BADGES.forEach(function(b){
|
||||
list.forEach(function(b){
|
||||
var e = b.cond(p);
|
||||
html += '<div class="badge '+(e?'earned':'locked-b')+'" title="'+(e?'Geschafft!':'Ziel: '+b.desc)+'">'
|
||||
+ '<div class="icon">'+b.icon+'</div><div class="name">'+b.name+'</div>'
|
||||
html += '<div class="badge '+(e?'earned':'locked-b')+(b.thematic?' thematic':'')+'" title="'+(e?'Geschafft!':'Ziel: '+b.desc)+'">'
|
||||
+ '<div class="icon"><img src="'+b.icon+'" alt="" loading="lazy"></div>'
|
||||
+ '<div class="name">'+b.name+'</div>'
|
||||
+ '<div class="desc">'+b.desc+'</div></div>';
|
||||
});
|
||||
html += '</div></div>';
|
||||
|
||||
@@ -412,6 +412,7 @@ schueler01,Max M.,,,,,</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="assets/js/badges.js"></script>
|
||||
<script>
|
||||
var BASE = (window.__GGS__?.baseUrl) || '';
|
||||
if (!BASE) { var p = location.pathname, i = p.indexOf('/geograsim'); if (i !== -1) { var rest = p.substring(i + '/geograsim'.length); BASE = rest.indexOf('/App/') === 0 ? p.substring(0, i) + '/geograsim/App' : p.substring(0, i) + '/geograsim'; } }
|
||||
@@ -3203,6 +3204,8 @@ function renderMatrix() {
|
||||
html += '<th class="r-mod" onclick="sortMatrix(\''+mod.id+'\')" title="'+esc(mod.name)+'">'+mod.icon+' '+arrow(mod.id)+'</th>';
|
||||
});
|
||||
html += '<th class="r-total" onclick="sortMatrix(\'total\')">Gesamt '+arrow('total')+'</th>';
|
||||
var hasBadges = (typeof ggsBadgeCount === 'function');
|
||||
if (hasBadges) html += '<th class="r-total" title="Freigeschaltete Abzeichen (Basis + thematisch)">Abzeichen</th>';
|
||||
html += '</tr></thead><tbody>';
|
||||
|
||||
students.forEach(function(s) {
|
||||
@@ -3220,6 +3223,15 @@ function renderMatrix() {
|
||||
});
|
||||
var t = m.totals[s.id] || {score:0, plays:0};
|
||||
html += '<td class="r-total"><b>'+t.score.toFixed(1)+'</b><span class="rp">'+t.plays+'×</span></td>';
|
||||
if (hasBadges) {
|
||||
var bc = 0, bmax = 0;
|
||||
m.modules.forEach(function(mod){
|
||||
var c = m.cells[s.id]?.[mod.id];
|
||||
bmax += ggsSimBadges(mod.id).length;
|
||||
if (c) bc += ggsBadgeCount({plays:c.plays, best_stars:c.stars, level:c.level}, mod.id);
|
||||
});
|
||||
html += '<td class="r-total"><b>'+bc+'</b><span class="rp">von '+bmax+'</span></td>';
|
||||
}
|
||||
html += '</tr>';
|
||||
});
|
||||
html += '</tbody></table></div></div>';
|
||||
|
||||