Files
geograsim/App/schueler.html
T
Adminator 33fb423967 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>
2026-05-07 01:20:53 +02:00

376 lines
20 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GeoGraSim — Meine Simulationen</title>
<link rel="icon" type="image/png" href="favicon-96x96.png" sizes="96x96"><link rel="icon" type="image/svg+xml" href="favicon.svg">
<link rel="stylesheet" href="assets/fonts/inter.css">
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'Inter',sans-serif;background:#f7f6f3;color:#1a1a1a;min-height:100vh}
.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:.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;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}
.wrap{max-width:900px;margin:0 auto;padding:1.2rem 1rem}
/* Tabs */
.tabs{display:flex;gap:0;margin-bottom:1.2rem;border-radius:10px;overflow:hidden;border:1.5px solid rgba(0,0,0,.06);background:#fff}
.tab{flex:1;padding:.6rem;text-align:center;font-size:.78rem;font-weight:700;cursor:pointer;color:#8a8a8a;transition:all .15s;border:none;background:transparent}
.tab.on{background:#4a7c8a;color:#fff}
/* Module grid */
.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 */
.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 .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)}
.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)}
.profile-avatar{font-size:3.5rem;cursor:pointer;transition:transform .15s}
.profile-avatar:hover{transform:scale(1.15)}
.profile-name{font-size:1.1rem;font-weight:800;margin-top:.3rem}
.profile-class{font-size:.75rem;color:#8a8a8a}
.profile-stats{display:flex;justify-content:center;gap:1.5rem;margin-top:.8rem}
.profile-stat .n{font-size:1.3rem;font-weight:900;color:#4a7c8a}
.profile-stat .l{font-size:.6rem;color:#8a8a8a}
.hidden{display:none}
@media(max-width:550px){.mod-grid{grid-template-columns:1fr}.badge-grid{grid-template-columns:repeat(2,1fr)}}
</style>
</head>
<body>
<div class="topbar">
<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>
</div>
<div class="wrap">
<!-- Tabs -->
<div class="tabs">
<div class="tab on" onclick="switchTab('modules')">Meine Module</div>
<div class="tab" onclick="switchTab('achievements')">Meine Erfolge</div>
<div class="tab" onclick="switchTab('profile')">Profil</div>
</div>
<div id="tab-modules"></div>
<div id="tab-achievements" class="hidden"></div>
<div id="tab-profile" class="hidden"></div>
</div>
<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'; } }
var profile = null;
var modules = [];
// 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'},
{id:'three_sims', icon:'🏅', name:'Dreifach', desc:'Drei verschiedene Simulationen gespielt'},
{id:'all_sims', icon:'🏆', name:'Komplett', desc:'Alle Module abgeschlossen'},
{id:'high_score', icon:'⭐', name:'Spitzenscore', desc:'Score über 80 in einer Simulation'},
{id:'speed_run', icon:'⚡', name:'Blitzschnell', desc:'Eine Simulation in unter 10 Minuten'},
{id:'explorer', icon:'🧭', name:'Entdecker', desc:'Alle Maßnahmen in einer Simulation ausprobiert'},
{id:'climate_hero', icon:'🌍', name:'Klimaheld', desc:'Temperaturanstieg unter 1.5°C gehalten'},
{id:'city_planner', icon:'🏙️', name:'Stadtplaner', desc:'Stadt mit über 90% Zufriedenheit gebaut'},
];
async function api(endpoint, data) {
var opts = data ? {method:'POST',headers:{'Content-Type':'application/json'},credentials:'same-origin',body:JSON.stringify(data)} : {credentials:'same-origin'};
var r = await fetch(BASE + '/api/' + endpoint, opts);
return await r.json();
}
function logout() { api('auth', {action:'logout'}).then(function() { location.href = 'login.html'; }); }
function switchTab(tab) {
document.querySelectorAll('.tab').forEach(function(t, i) {
var tabs = ['modules','achievements','profile'];
t.classList.toggle('on', tabs[i] === tab);
});
document.getElementById('tab-modules').classList.toggle('hidden', tab !== 'modules');
document.getElementById('tab-achievements').classList.toggle('hidden', tab !== 'achievements');
document.getElementById('tab-profile').classList.toggle('hidden', tab !== 'profile');
}
async function init() {
var status = await api('auth', {action:'status'});
if (status.role !== 'student') { location.href = 'login.html'; return; }
document.getElementById('user-name').textContent = status.session?.display_name || 'Schüler';
// Load profile
var pr = await api('profile');
profile = pr.profile;
if (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();
renderAchievements();
renderProfile();
}
function buildModuleUrl(mod) {
// Bevorzugt play_url aus module_info; Fallback: module_id
var base = mod.play_url || mod.id;
// Relativ zum aktuellen Front-Controller-Pfad — Apache-Rewrite macht den Rest
var url = base.indexOf('/') === 0 ? base : base;
if (mod.mode === 'teacher_started' && mod.forcedLevel) {
url += (url.indexOf('?') === -1 ? '?' : '&') + 'level=' + mod.forcedLevel;
}
return url;
}
async function renderModules() {
var html = '';
var modList = [];
try {
modList = await api('modules?student=1');
if (modList.error) modList = [];
} catch(e) {}
// 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">📋 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>' : '';
var url = buildModuleUrl(m);
html += '<a href="'+url+'" class="mod-card" style="border-color:#e8833a;border-width:2px;margin-bottom:.5rem">';
html += '<div class="mod-img" style="height:80px;background:linear-gradient(135deg,#e8833a,#f4a261)"><span style="font-size:2.5rem">'+(m.icon||'📚')+'</span></div>';
html += '<div class="mod-body">';
html += '<h3 style="color:#e8833a">'+m.icon+' '+m.name+(m.forcedLevel ? ' — Stufe '+m.forcedLevel : '')+'</h3>';
html += '<p>Auftrag von deiner Lehrperson'+due+quiz+'</p>';
html += '<span class="mod-status" style="background:#fde8d0;color:#e8833a">▶ Jetzt starten</span>';
html += '</div></a>';
});
html += '</div>';
}
// Regulaere Module: spielbar zuerst, "in Kürze" am Ende.
function renderModCard(m) {
var isComing = m.mode === 'coming_soon';
var isLocked = m.mode === 'locked';
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;
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() {
var allProgress = [];
try {
var resp = await api('progress?student_id=0');
if (Array.isArray(resp)) allProgress = resp;
} catch(e) {}
var totalPlays = 0, totalXP = 0, bestStarsOverall = 0;
allProgress.forEach(function(p) {
totalPlays += parseInt(p.plays || 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 (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 = '';
// 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;
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>';
document.getElementById('tab-achievements').innerHTML = html;
}
async function renderProfile() {
if (!profile) {
document.getElementById('tab-profile').innerHTML = '<p style="color:#8a8a8a">Profil wird geladen…</p>';
return;
}
// Lade Fortschritt
var totalPlays = 0, totalXP = 0, maxLevel = 1;
try {
var prog = await api('progress?student_id=0');
if (Array.isArray(prog)) prog.forEach(function(p) {
totalPlays += parseInt(p.plays||0);
totalXP += parseInt(p.xp||0);
maxLevel = Math.max(maxLevel, parseInt(p.level||1));
});
} catch(e) {}
var html = '<div class="profile-card">';
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">';
html += '<div class="profile-stat"><div class="n">'+totalPlays+'</div><div class="l">Simulationen</div></div>';
html += '<div class="profile-stat"><div class="n">'+totalXP+'</div><div class="l">XP</div></div>';
html += '<div class="profile-stat"><div class="n">Level '+maxLevel+'</div><div class="l">Höchstes Level</div></div>';
html += '</div></div>';
html += '<div style="text-align:center;margin-top:1rem"><a href="profil.html" class="tab" style="display:inline-block;padding:.5rem 1.2rem;background:#4a7c8a;color:#fff;border-radius:8px;text-decoration:none;font-size:.78rem;font-weight:700">Profil bearbeiten</a></div>';
document.getElementById('tab-profile').innerHTML = html;
}
init();
</script>
<script src="assets/js/version-tag.js" defer></script>
</body>
</html>