Atlas: Live-View-Feature + Submit-Bug-Fixes + Admin-Styleguide
- Neuer API-Endpoint /api/live (heartbeat / spectator) + DB-Tabelle live_sessions + class_modules.paused-Spalte - Plattform-Live-Client (assets/js/live-client.js): Heartbeat, Pause-Overlay, Spectator-Mode bei ?view=teacher - ggs_inject_live() in _scripts.php als Helper, in alle 11 Sim-Wrapper integriert - Lehrer-Cockpit: tabellarische Klassen-Live-Ansicht pro Sim, Pause-Toggle, blinkender Tab-Indikator wenn aktive Sessions - Submit-Bug erschlagen: progress.php hat jetzt submit_assessment- und reflection-Action; saves.php akzeptiert beide Key-Konventionen; alle Direkt-Pfad-API-Files mit require_once-Bootstrap - Avatar-Default: zufaelliger DALL-E-Avatar fuer neue Schueler:innen - Admin-Styleguide-Seite mit Bildstil, Farb-Tokens, Layout, iPad-Pattern - Klima 2D + 3D: GGS_LIVE_STATE-Hook, Klima 2D zusaetzlich skipResume fuer Klassenaufgaben-Reset - Test-Lehrer Jakob + 3 Schueler:innen lokal + auf Live angelegt - Rundbriefe an alle Sim-Instanzen + Submit-Briefe an sonnensystem, busfahrt, energiemanager + Glossar-Anfrage zu Grundriess - Status-Uebergabe in _inbox/zentrale/_status.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+146
-73
@@ -13,9 +13,10 @@
|
||||
.topbar{background:#fff;padding:.55rem 1.2rem;display:flex;align-items:center;gap:.7rem;border-bottom:1px solid rgba(0,0,0,.06);position:sticky;top:0;z-index:10}
|
||||
.topbar .logo{height:28px}
|
||||
.topbar .spacer{flex:1}
|
||||
.topbar .user-pill{display:flex;align-items:center;gap:.35rem;padding:.25rem .6rem .25rem .3rem;background:#dae8ec;border-radius:99px;font-size:.72rem;font-weight:600;color:#4a7c8a;text-decoration:none;cursor:pointer}
|
||||
.topbar .user-pill{display:flex;align-items:center;gap:.35rem;padding:.15rem .6rem .15rem .15rem;background:#dae8ec;border-radius:99px;font-size:.72rem;font-weight:600;color:#4a7c8a;text-decoration:none;cursor:pointer}
|
||||
.topbar .user-pill:hover{background:#c8dce2}
|
||||
.topbar .user-pill .emoji{font-size:1.2rem}
|
||||
.topbar .user-pill .emoji{font-size:1.2rem;width:28px;height:28px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;background:#fff;overflow:hidden}
|
||||
.topbar .user-pill .emoji img{width:100%;height:100%;object-fit:cover;display:block}
|
||||
.topbar .btn-sm{padding:.25rem .55rem;border:1px solid rgba(0,0,0,.08);border-radius:6px;background:#fff;font-size:.65rem;font-weight:600;cursor:pointer;color:#4a4a4a;text-decoration:none}
|
||||
.topbar .btn-sm:hover{background:#dae8ec;color:#4a7c8a}
|
||||
|
||||
@@ -27,20 +28,25 @@
|
||||
.tab.on{background:#4a7c8a;color:#fff}
|
||||
|
||||
/* Module grid */
|
||||
.mod-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:.8rem}
|
||||
.mod-card{background:#fff;border-radius:12px;overflow:hidden;border:1.5px solid rgba(0,0,0,.05);transition:all .25s;cursor:pointer;text-decoration:none;color:inherit;display:block}
|
||||
.mod-card:hover{transform:translateY(-3px);box-shadow:0 8px 25px rgba(74,124,138,.1);border-color:rgba(74,124,138,.2)}
|
||||
.mod-card.locked{opacity:.55;cursor:default}
|
||||
.mod-card.locked:hover{transform:none;box-shadow:none}
|
||||
.mod-img{height:110px;background:linear-gradient(135deg,#dae8ec,#c8dce2);display:flex;align-items:center;justify-content:center;font-size:2.5rem;position:relative}
|
||||
.mod-img img{width:100%;height:100%;object-fit:cover}
|
||||
.mod-body{padding:.7rem}
|
||||
.mod-body h3{font-size:.82rem;font-weight:700;margin-bottom:.15rem}
|
||||
.mod-body p{font-size:.68rem;color:#4a4a4a;margin-bottom:.4rem}
|
||||
.mod-status{display:inline-flex;align-items:center;gap:.25rem;font-size:.6rem;font-weight:600;padding:2px 8px;border-radius:5px}
|
||||
.mod-status.available{background:#dceadd;color:#5a8a5e}
|
||||
.mod-status.locked-s{background:#f0eeea;color:#8a8a8a}
|
||||
.mod-status.done{background:#dae8ec;color:#4a7c8a}
|
||||
.mod-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:1rem}
|
||||
.mod-card{background:#fff;border-radius:12px;overflow:hidden;border:1.5px solid rgba(0,0,0,.05);transition:all .25s;display:flex;flex-direction:column}
|
||||
.mod-card.spielbar{cursor:default}
|
||||
.mod-card.locked{opacity:.55}
|
||||
.mod-card.coming{opacity:.7}
|
||||
.mod-card.coming .mod-img{filter:grayscale(.5)}
|
||||
.coming-icon{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:1rem;background:rgba(255,255,255,.92);border-radius:8px;padding:.3rem .55rem;font-weight:700;color:#8a6a3a;white-space:nowrap}
|
||||
.coming-divider{margin:1.4rem 0 .8rem;font-size:.74rem;font-weight:700;color:#8a8a8a;text-transform:uppercase;letter-spacing:.06em;padding:.4rem .2rem;border-top:1px solid rgba(0,0,0,.06)}
|
||||
.mod-img{height:210px;background:linear-gradient(135deg,#dae8ec,#c8dce2);display:flex;align-items:center;justify-content:center;font-size:2.5rem;position:relative;overflow:hidden}
|
||||
.mod-img img{width:100%;height:100%;object-fit:cover;transition:transform .35s}
|
||||
.mod-card.spielbar:hover .mod-img img{transform:scale(1.04)}
|
||||
.mod-body{padding:.7rem .8rem .9rem;flex:1;display:flex;flex-direction:column}
|
||||
.mod-body h3{font-size:.92rem;font-weight:700;margin-bottom:.25rem;line-height:1.2}
|
||||
.mod-body p{font-size:.72rem;color:#4a4a4a;margin-bottom:.6rem;line-height:1.4;flex:1}
|
||||
.mod-actions{display:flex;flex-wrap:wrap;gap:.35rem;margin-top:auto}
|
||||
.mod-btn{display:inline-flex;align-items:center;gap:.25rem;padding:.42rem .7rem;border-radius:7px;font-size:.7rem;font-weight:600;text-decoration:none;border:1.5px solid rgba(0,0,0,.08);background:#f7f6f3;color:#4a4a4a;transition:all .15s;min-height:32px;white-space:nowrap}
|
||||
.mod-btn:hover{background:#fff;border-color:rgba(74,124,138,.4);color:#1a1a1a}
|
||||
.mod-btn.primary{background:#4a7c8a;color:#fff;border-color:#4a7c8a;flex:1;justify-content:center}
|
||||
.mod-btn.primary:hover{background:#3d6671;color:#fff}
|
||||
.lock-icon{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:1.5rem;background:rgba(255,255,255,.9);border-radius:8px;padding:.3rem .6rem}
|
||||
|
||||
/* Erfolge */
|
||||
@@ -51,6 +57,22 @@
|
||||
.badge .desc{font-size:.6rem;color:#8a8a8a}
|
||||
.badge.earned{border-color:#4a7c8a;background:linear-gradient(135deg,#f0f8fa,#fff)}
|
||||
.badge.locked-b{opacity:.4}
|
||||
/* Erfolgs-Liste pro Modul */
|
||||
.ach-summary{display:grid;grid-template-columns:repeat(3,1fr);gap:.6rem;margin-bottom:1.2rem}
|
||||
.ach-summary .card{background:#fff;border-radius:10px;padding:.9rem .8rem;text-align:center;border:1.5px solid rgba(0,0,0,.05)}
|
||||
.ach-summary .n{font-size:1.6rem;font-weight:900;color:#4a7c8a;line-height:1}
|
||||
.ach-summary .l{font-size:.62rem;color:#8a8a8a;margin-top:.25rem;text-transform:uppercase;letter-spacing:.04em}
|
||||
.ach-modules{display:flex;flex-direction:column;gap:.5rem;margin-bottom:1.4rem}
|
||||
.ach-mod{background:#fff;border-radius:10px;padding:.7rem .9rem;display:flex;align-items:center;gap:.7rem;border:1.5px solid rgba(0,0,0,.05)}
|
||||
.ach-mod .ic{font-size:1.5rem;width:36px;text-align:center;flex-shrink:0}
|
||||
.ach-mod .info{flex:1;min-width:0}
|
||||
.ach-mod .info h4{font-size:.85rem;font-weight:700;margin:0 0 .15rem 0}
|
||||
.ach-mod .info .meta{font-size:.65rem;color:#8a8a8a}
|
||||
.ach-mod .stats{display:flex;gap:.5rem;flex-shrink:0}
|
||||
.ach-mod .stat-pill{background:#f0eeea;color:#4a4a4a;padding:.2rem .55rem;border-radius:6px;font-size:.62rem;font-weight:700}
|
||||
.ach-mod .stat-pill.stars{background:#fff4d6;color:#a37800}
|
||||
.ach-mod .stat-pill.lvl{background:#dae8ec;color:#4a7c8a}
|
||||
.ach-empty{background:#fff;border-radius:10px;padding:1.5rem 1rem;text-align:center;border:1.5px dashed rgba(0,0,0,.1);color:#8a8a8a;font-size:.78rem}
|
||||
|
||||
/* Profil-Karte */
|
||||
.profile-card{background:#fff;border-radius:12px;padding:1.5rem;text-align:center;border:1.5px solid rgba(0,0,0,.04)}
|
||||
@@ -68,7 +90,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<div class="topbar">
|
||||
<img src="assets/img/geograsim_t.svg" alt="GeoGraSim" class="logo">
|
||||
<a href="schueler" aria-label="Cockpit"><img src="assets/img/geograsim_t.svg" alt="GeoGraSim" class="logo"></a>
|
||||
<span class="spacer"></span>
|
||||
<a href="profil.html" class="user-pill" id="user-pill"><span class="emoji">🧑🎓</span><span id="user-name">…</span></a>
|
||||
<button class="btn-sm" onclick="logout()">Abmelden</button>
|
||||
@@ -94,20 +116,9 @@ if (!BASE) { var p = location.pathname, i = p.indexOf('/geograsim'); if (i !== -
|
||||
var profile = null;
|
||||
var modules = [];
|
||||
|
||||
// Module kommen aus /api/modules?student=1 (inkl. play_url, icon, name, mode).
|
||||
// card_image + Fallback-Beschreibung ergaenzen wir clientseitig.
|
||||
var CARD_IMAGES = {
|
||||
'klima': 'assets/img/card-climate.png',
|
||||
'klima-3d': 'assets/img/card-climate.png',
|
||||
'fluss': 'assets/img/card-fluss.png',
|
||||
'heli': 'assets/img/card-heli.png',
|
||||
'stadt': 'assets/img/card-city.png',
|
||||
'erdbeben': 'assets/img/card-tectonic.png',
|
||||
'energiemix': 'assets/img/card-energy.png',
|
||||
'lieferketten': 'assets/img/card-globe.png',
|
||||
'regenwald': 'assets/img/card-climate.png',
|
||||
'logistik': 'assets/img/card-globe.png'
|
||||
};
|
||||
// Module kommen aus /api/modules?student=1 (inkl. play_url, icon, name, mode,
|
||||
// card_image, desc). Bild-Pfad ist ein Dateiname wie "card-farmer.png" und wird
|
||||
// gegen "assets/img/" relativ zur App-Basis aufgeloest.
|
||||
|
||||
var BADGES = [
|
||||
{id:'first_sim', icon:'🎯', name:'Erste Simulation', desc:'Eine Simulation abgeschlossen'},
|
||||
@@ -148,7 +159,14 @@ async function init() {
|
||||
var pr = await api('profile');
|
||||
profile = pr.profile;
|
||||
if (profile?.emoji_avatar) {
|
||||
document.querySelector('#user-pill .emoji').textContent = profile.emoji_avatar;
|
||||
var v = profile.emoji_avatar;
|
||||
var pillEmoji = document.querySelector('#user-pill .emoji');
|
||||
if (typeof v === 'string' && v.indexOf('avatar:') === 0) {
|
||||
var slug = v.substring(7);
|
||||
pillEmoji.innerHTML = '<img src="assets/img/avatars/avatar-' + slug + '.png" alt="">';
|
||||
} else {
|
||||
pillEmoji.textContent = v;
|
||||
}
|
||||
}
|
||||
|
||||
await renderModules();
|
||||
@@ -175,11 +193,13 @@ async function renderModules() {
|
||||
if (modList.error) modList = [];
|
||||
} catch(e) {}
|
||||
|
||||
// Lehrer-gesteuerte Auftraege zuerst
|
||||
var teacherStarted = modList.filter(function(m) { return m.mode === 'teacher_started'; });
|
||||
// Lehrer-gesteuerte Auftraege zuerst — erledigte Auftraege fallen automatisch
|
||||
// in die regulaere Modul-Liste, damit der Auftrag aus dem Cockpit verschwindet,
|
||||
// das Modul aber spielbar bleibt.
|
||||
var teacherStarted = modList.filter(function(m) { return m.mode === 'teacher_started' && !m.assignmentCompleted; });
|
||||
if (teacherStarted.length > 0) {
|
||||
html += '<div style="margin-bottom:1rem">';
|
||||
html += '<p style="font-size:.78rem;font-weight:700;color:#e8833a;margin-bottom:.4rem">📋 Auftraege von deiner Lehrperson</p>';
|
||||
html += '<p style="font-size:.78rem;font-weight:700;color:#e8833a;margin-bottom:.4rem">📋 Aufträge von deiner Lehrperson</p>';
|
||||
teacherStarted.forEach(function(m) {
|
||||
var due = m.dueDate ? '<br><span style="font-size:.6rem;color:#8a8a8a">Zu erledigen bis ' + new Date(m.dueDate).toLocaleDateString('de-AT',{day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'}) + '</span>' : '';
|
||||
var quiz = m.quizEnabled ? '<br><span style="font-size:.6rem;color:#4a7c8a">📝 Mit kurzem Quiz</span>' : '';
|
||||
@@ -195,64 +215,113 @@ async function renderModules() {
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
// Regulaere Module (free + locked)
|
||||
html += '<div class="mod-grid">';
|
||||
modList.filter(function(m) { return m.mode !== 'teacher_started'; }).forEach(function(m) {
|
||||
// Regulaere Module: spielbar zuerst, "in Kürze" am Ende.
|
||||
function renderModCard(m) {
|
||||
var isComing = m.mode === 'coming_soon';
|
||||
var isLocked = m.mode === 'locked';
|
||||
var cardImg = CARD_IMAGES[m.id];
|
||||
var isSpielbar = !isComing && !isLocked;
|
||||
var cardImg = m.card_image ? 'assets/img/' + m.card_image : null;
|
||||
var imgHtml = cardImg ? '<img src="'+cardImg+'" alt="">' : '<span style="font-size:3rem">'+(m.icon||'📚')+'</span>';
|
||||
var url = buildModuleUrl(m);
|
||||
var moduleId = m.id;
|
||||
var infoHref = 'modul-' + moduleId;
|
||||
var glossarHref = 'glossar?module=' + moduleId;
|
||||
|
||||
if (isLocked) {
|
||||
html += '<div class="mod-card locked">';
|
||||
html += '<div class="mod-img" style="position:relative">'+imgHtml+'<div class="lock-icon">🔒</div></div>';
|
||||
} else {
|
||||
html += '<a href="'+url+'" class="mod-card">';
|
||||
html += '<div class="mod-img">'+imgHtml+'</div>';
|
||||
}
|
||||
html += '<div class="mod-body">';
|
||||
html += '<h3>'+(m.icon||'📚')+' '+m.name+'</h3>';
|
||||
html += '<p>'+(m.desc||'')+'</p>';
|
||||
if (isLocked) {
|
||||
html += '<span class="mod-status locked-s">🔒 Gesperrt</span>';
|
||||
html += '</div></div>';
|
||||
} else {
|
||||
html += '<span class="mod-status available">▶ Starten</span>';
|
||||
html += '</div></a>';
|
||||
}
|
||||
});
|
||||
var cls = isComing ? 'coming' : (isLocked ? 'locked' : 'spielbar');
|
||||
var s = '<div class="mod-card '+cls+'">';
|
||||
var overlay = isComing ? '<div class="coming-icon">🚧 In Kürze</div>'
|
||||
: isLocked ? '<div class="lock-icon">🔒</div>'
|
||||
: '';
|
||||
s += '<div class="mod-img">'+imgHtml+overlay+'</div>';
|
||||
s += '<div class="mod-body">';
|
||||
s += '<h3>'+(m.icon||'📚')+' '+m.name+'</h3>';
|
||||
s += '<p>'+(m.desc||'')+'</p>';
|
||||
s += '<div class="mod-actions">';
|
||||
if (isSpielbar) s += '<a href="'+url+'" class="mod-btn primary">▶ Starten</a>';
|
||||
s += '<a href="'+infoHref+'" class="mod-btn" title="Mehr Infos">📖 Infos</a>';
|
||||
if (!isComing) s += '<a href="'+glossarHref+'" class="mod-btn" title="Glossar zu diesem Modul">📚 Glossar</a>';
|
||||
s += '</div>';
|
||||
s += '</div></div>';
|
||||
return s;
|
||||
}
|
||||
|
||||
// Regulaere Liste enthaelt alles ausser noch offene Lehrer-Auftraege.
|
||||
// Erledigte Auftraege erscheinen hier als spielbar, damit der Schueler sie
|
||||
// freiwillig wiederholen kann.
|
||||
var regular = modList.filter(function(m) { return m.mode !== 'teacher_started' || m.assignmentCompleted; });
|
||||
var spielbar = regular.filter(function(m) { return m.mode !== 'coming_soon'; });
|
||||
var coming = regular.filter(function(m) { return m.mode === 'coming_soon'; });
|
||||
|
||||
html += '<div class="mod-grid">';
|
||||
spielbar.forEach(function(m) { html += renderModCard(m); });
|
||||
html += '</div>';
|
||||
|
||||
if (coming.length > 0) {
|
||||
html += '<div class="coming-divider">In Kürze verfügbar</div>';
|
||||
html += '<div class="mod-grid">';
|
||||
coming.forEach(function(m) { html += renderModCard(m); });
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
document.getElementById('tab-modules').innerHTML = html;
|
||||
}
|
||||
|
||||
// Modul-Beschreibungen kommen jetzt aus module_info.short_desc via API.
|
||||
|
||||
async function renderAchievements() {
|
||||
// Lade echte Fortschrittsdaten
|
||||
var progressData = {};
|
||||
var allProgress = [];
|
||||
try {
|
||||
var allProgress = await api('progress?student_id=0'); // eigener Fortschritt
|
||||
if (Array.isArray(allProgress)) {
|
||||
allProgress.forEach(function(p) { progressData[p.sim_id] = p; });
|
||||
}
|
||||
var resp = await api('progress?student_id=0');
|
||||
if (Array.isArray(resp)) allProgress = resp;
|
||||
} catch(e) {}
|
||||
|
||||
var totalPlays = 0, totalStars = 0, totalXP = 0;
|
||||
Object.values(progressData).forEach(function(p) {
|
||||
var totalPlays = 0, totalXP = 0, bestStarsOverall = 0;
|
||||
allProgress.forEach(function(p) {
|
||||
totalPlays += parseInt(p.plays || 0);
|
||||
totalStars = Math.max(totalStars, parseInt(p.best_stars || 0));
|
||||
totalXP += parseInt(p.xp || 0);
|
||||
bestStarsOverall = Math.max(bestStarsOverall, parseInt(p.best_stars || 0));
|
||||
});
|
||||
var simCount = allProgress.length;
|
||||
|
||||
var earned = [];
|
||||
if (totalPlays >= 1) earned.push('first_sim');
|
||||
if (Object.keys(progressData).length >= 3) earned.push('three_sims');
|
||||
if (Object.keys(progressData).length >= 8) earned.push('all_sims');
|
||||
if (totalStars >= 4) earned.push('high_score');
|
||||
if (totalXP >= 100) earned.push('speed_run'); // repurposed as XP badge
|
||||
if (simCount >= 3) earned.push('three_sims');
|
||||
if (simCount >= 8) earned.push('all_sims');
|
||||
if (bestStarsOverall >= 4) earned.push('high_score');
|
||||
if (totalXP >= 100) earned.push('speed_run');
|
||||
|
||||
var html = '<div style="margin-bottom:.8rem"><p style="font-size:.85rem;font-weight:700;color:#4a7c8a">Deine Erfolge</p>';
|
||||
html += '<p style="font-size:.72rem;color:#8a8a8a">Spiele Simulationen und sammle Auszeichnungen.</p></div>';
|
||||
var html = '';
|
||||
|
||||
// Top-Kennzahlen
|
||||
html += '<div class="ach-summary">';
|
||||
html += '<div class="card"><div class="n">'+simCount+'</div><div class="l">Module begonnen</div></div>';
|
||||
html += '<div class="card"><div class="n">'+totalPlays+'</div><div class="l">Durchgänge</div></div>';
|
||||
html += '<div class="card"><div class="n">'+totalXP+'</div><div class="l">XP gesamt</div></div>';
|
||||
html += '</div>';
|
||||
|
||||
// Modul-Erfolge-Liste
|
||||
html += '<p style="font-size:.85rem;font-weight:700;color:#4a7c8a;margin-bottom:.5rem">Was du in den Modulen geschafft hast</p>';
|
||||
if (allProgress.length === 0) {
|
||||
html += '<div class="ach-empty">Noch keine Module gespielt. Geh auf <strong>Meine Module</strong> und starte einen Durchgang.</div>';
|
||||
} else {
|
||||
html += '<div class="ach-modules">';
|
||||
allProgress.forEach(function(p) {
|
||||
var stars = '★'.repeat(p.best_stars || 0) + '☆'.repeat(Math.max(0, 5 - (p.best_stars || 0)));
|
||||
var last = p.last_played_at ? new Date(p.last_played_at.replace(' ','T')+'Z').toLocaleDateString('de-AT', {day:'2-digit',month:'2-digit',year:'2-digit'}) : '—';
|
||||
html += '<div class="ach-mod">';
|
||||
html += '<div class="ic">'+(p.icon || '📚')+'</div>';
|
||||
html += '<div class="info"><h4>'+(p.title || p.sim_id)+'</h4>';
|
||||
html += '<div class="meta">'+p.plays+'× gespielt · '+p.xp+' XP · zuletzt '+last+'</div></div>';
|
||||
html += '<div class="stats">';
|
||||
html += '<span class="stat-pill stars" title="Beste Sterne">'+stars+'</span>';
|
||||
html += '<span class="stat-pill lvl">Lvl '+(p.level || 1)+'</span>';
|
||||
html += '</div></div>';
|
||||
});
|
||||
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;
|
||||
@@ -283,7 +352,11 @@ async function renderProfile() {
|
||||
} catch(e) {}
|
||||
|
||||
var html = '<div class="profile-card">';
|
||||
html += '<div class="profile-avatar" onclick="location.href=\'profil.html\'">'+(profile.emoji_avatar || '🧑🎓')+'</div>';
|
||||
var pAv = profile.emoji_avatar || '🧑🎓';
|
||||
var pAvHtml = (typeof pAv === 'string' && pAv.indexOf('avatar:') === 0)
|
||||
? '<img src="assets/img/avatars/avatar-' + pAv.substring(7) + '.png" alt="" style="width:100%;height:100%;border-radius:50%;object-fit:cover">'
|
||||
: pAv;
|
||||
html += '<div class="profile-avatar" onclick="location.href=\'profil.html\'" style="width:80px;height:80px;border-radius:50%;background:#dae8ec;display:flex;align-items:center;justify-content:center;margin:0 auto;overflow:hidden">'+pAvHtml+'</div>';
|
||||
html += '<div class="profile-name">'+(profile.display_name || profile.username)+'</div>';
|
||||
html += '<div class="profile-class">Klasse '+(profile.class_name || '—')+'</div>';
|
||||
html += '<div class="profile-stats">';
|
||||
|
||||
Reference in New Issue
Block a user