Update index.html

This commit is contained in:
denshooter
2025-08-29 18:01:00 +02:00
committed by GitHub
parent 186bae3328
commit ee790ac2ab

View File

@@ -4,96 +4,100 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
<meta http-equiv="Cache-Control" content="no-store" /> <meta http-equiv="Cache-Control" content="no-store" />
<meta name="color-scheme" content="dark light" />
<title>Verbinde …</title> <title>Verbinde …</title>
<style> <style>
:root{ :root{
--bg:#0b0f14; --card:#0f1620; --muted:#8aa0b3; --accent:#73d7ff; --ok:#8affc1; --warn:#ffc46b; --bg:#0b0f14; --bg2:#0d1420; --card:#0f1620; --card2:#0c121a;
--stroke:#1a2634; --muted:#8aa0b3; --text:#e8f0f7;
--accent:#73d7ff; --ok:#8affc1;
} }
*{box-sizing:border-box} html,body{height:100%} *{box-sizing:border-box} html,body{height:100%}
body{ body{
margin:0; font:16px/1.45 system-ui,-apple-system,"Segoe UI",Roboto,Ubuntu,"Helvetica Neue",Arial; margin:0; color:var(--text);
color:#e8f0f7; background: font:16px/1.45 system-ui,-apple-system,"Segoe UI",Roboto,Ubuntu,"Helvetica Neue",Arial;
background:
radial-gradient(1200px 800px at 80% -10%,#132033 0%,transparent 60%), radial-gradient(1200px 800px at 80% -10%,#132033 0%,transparent 60%),
linear-gradient(180deg,#0b0f14,#0b0f14 60%,#0d1420); linear-gradient(180deg,var(--bg),var(--bg) 60%,var(--bg2));
} }
.container{min-height:100%; display:flex; align-items:center; justify-content:center; padding:24px} .container{min-height:100%; display:flex; align-items:center; justify-content:center; padding:24px}
.card{ .card{
width:min(960px,94vw); background:linear-gradient(180deg,var(--card),#0c121a); width:min(980px,94vw);
border:1px solid #1a2634; border-radius:16px; padding:26px 28px; box-shadow:0 10px 30px rgba(0,0,0,.35) background:linear-gradient(180deg,var(--card),var(--card2));
border:1px solid var(--stroke); border-radius:16px; padding:26px 28px;
box-shadow:0 10px 30px rgba(0,0,0,.35)
} }
h1{margin:0 0 8px; font-size:28px; letter-spacing:.2px} h1{margin:0 0 8px; font-size:28px; letter-spacing:.2px}
.meta{display:flex; flex-wrap:wrap; gap:10px 18px; color:var(--muted); font-size:14px; margin-bottom:18px}
.badge{border:1px solid #243447; padding:6px 10px; border-radius:999px; background:#111926}
.grid{display:grid; grid-template-columns:1fr 1fr; gap:18px}
.box{border:1px solid #1a2634; border-radius:14px; padding:16px; background:#0c131c}
a{color:var(--accent); text-decoration:none}
.logo{font-weight:700; letter-spacing:.5px} .logo{font-weight:700; letter-spacing:.5px}
.meta{display:flex; flex-wrap:wrap; gap:10px 12px; color:var(--muted); font-size:14px; margin-bottom:12px}
.badge{border:1px solid var(--stroke); padding:6px 10px; border-radius:999px; background:#111926}
.grid{display:grid; grid-template-columns:1fr 1fr; gap:18px}
@media (max-width:760px){.grid{grid-template-columns:1fr}}
.box{border:1px solid var(--stroke); border-radius:14px; padding:16px; background:var(--card2)}
.box strong{display:block; margin-bottom:6px}
a{color:var(--accent); text-decoration:none}
/* Stepper */ /* Phasen-Chips */
.stepper{display:flex; align-items:center; gap:12px; margin-bottom:10px} .phases{list-style:none;padding:0;margin:10px 0 12px;display:flex;gap:8px;flex-wrap:wrap}
.step{display:flex; align-items:center; gap:10px; color:var(--muted); font-size:14px} .phases li{padding:6px 10px;border:1px solid var(--stroke);border-radius:999px;background:var(--card2);opacity:.55;transition:opacity .25s ease, box-shadow .25s ease}
.dot{width:14px; height:14px; border-radius:50%; background:#1a2534; border:1px solid #253247} .phases li.on{opacity:1; box-shadow:0 0 0 9999px inset rgba(115,215,255,.07)}
.step.active .dot{background:var(--accent); box-shadow:0 0 0 4px rgba(115,215,255,.18)}
.step.done .dot{background:var(--ok)}
.line{height:2px; flex:1; background:#1a2534}
/* Bars */ /* Fortschrittsbalken */
.bar{height:10px; background:#0b1420; border-radius:999px; overflow:hidden; position:relative; margin-top:8px} .row{display:grid; gap:12px}
.label{display:flex; justify-content:space-between; gap:12px; font-size:14px; color:#cfe7ff}
.sub{color:var(--muted); font-size:12px; margin-top:4px; white-space:nowrap; text-overflow:ellipsis; overflow:hidden}
.bar{height:10px; background:#0b1420; border-radius:999px; overflow:hidden; position:relative}
.fill{height:100%; width:0%; background:linear-gradient(90deg,var(--accent),var(--ok)); transition:width .15s ease-in-out} .fill{height:100%; width:0%; background:linear-gradient(90deg,var(--accent),var(--ok)); transition:width .15s ease-in-out}
.indet::before{ .bar.indet::before{
content:""; position:absolute; inset:0; content:""; position:absolute; inset:0;
background:linear-gradient(90deg, transparent 0%, rgba(255,255,255,.15) 35%, transparent 70%); background:linear-gradient(90deg, transparent 0%, rgba(255,255,255,.15) 35%, transparent 70%);
animation:flow 1.3s linear infinite; animation:flow 1.2s linear infinite;
} }
@keyframes flow{0%{transform:translateX(-100%)}100%{transform:translateX(100%)}} @keyframes flow{0%{transform:translateX(-100%)}100%{transform:translateX(100%)}}
.row{display:grid; gap:10px}
.label{display:flex; justify-content:space-between; gap:12px; font-size:14px; color:#cfe3f5}
.sub{color:var(--muted); font-size:12px; margin-top:4px; white-space:nowrap; text-overflow:ellipsis; overflow:hidden}
.kbd{border:1px solid #2a3a4f; background:#0b1320; border-radius:6px; padding:2px 6px} .kbd{border:1px solid #2a3a4f; background:#0b1320; border-radius:6px; padding:2px 6px}
.tip{opacity:.95; font-size:14px} .tip{opacity:.95; font-size:14px}
.footer{display:flex; gap:12px; flex-wrap:wrap; margin-top:18px; color:var(--muted); font-size:13px} .footer{display:flex; gap:12px; flex-wrap:wrap; margin-top:12px; color:var(--muted); font-size:13px}
.small{font-size:12px; color:#9fb3c9} .small{font-size:12px; color:#9fb3c9}
</style> </style>
</head> </head>
<body> <body>
<noscript><div style="padding:12px;background:#300;border-bottom:1px solid #633;color:#fff;font:14px/1.3 monospace">
Dieses Overlay benötigt JavaScript, um Server/Map/Modus und Fortschritt zu zeigen.</div></noscript>
<div class="container"><div class="card"> <div class="container"><div class="card">
<!-- Kopfzeile -->
<div class="meta"> <div class="meta">
<span class="badge">Server: <span id="sv"></span></span> <span class="badge">Server: <span id="sv"></span></span>
<span class="badge">Map: <span id="map"></span></span> <span class="badge">Map: <span id="map"></span></span>
<span class="badge">Modus: <span id="gm"></span></span> <span class="badge">Modus: <span id="gm"></span></span>
<span class="badge">Du: <span id="me"></span></span> <span class="badge">Du: <span id="me"></span></span>
</div> </div>
<h1 class="logo">Willkommen auf <span id="sv2">deinem Server</span> 👋</h1> <h1 class="logo">Willkommen auf <span id="sv2">deinem Server</span> 👋</h1>
<!-- Stepper --> <!-- Phasen -->
<div class="stepper"> <ul class="phases">
<div class="step active" id="st-work"><div class="dot"></div>Workshop</div> <li id="ph-work" class="on">Workshop prüfen</li>
<div class="line"></div> <li id="ph-srv">Server-Content</li>
<div class="step" id="st-srv"><div class="dot"></div>Server-Content</div> <li id="ph-init">Initialisierung</li>
<div class="line"></div> </ul>
<div class="step" id="st-init"><div class="dot"></div>Initialisierung</div>
</div>
<div class="grid"> <div class="grid">
<!-- Fortschritt -->
<div class="box"> <div class="box">
<strong>Fortschritt</strong> <strong>Fortschritt</strong>
<div class="row" style="margin-top:6px"> <div class="row" style="margin-top:6px">
<!-- Workshop -->
<div> <div>
<div class="label"><span>Workshop-Downloads</span><span id="w-pct">0%</span></div> <div class="label"><span>Workshop</span><span id="w-pct">0%</span></div>
<div class="bar" id="w-bar"><div class="fill" id="w-fill"></div></div> <div class="bar" id="w-bar"><div class="fill" id="w-fill"></div></div>
<div class="sub" id="w-sub"></div> <div class="sub" id="w-sub">Warte auf Daten …</div>
</div> </div>
<!-- Server -->
<div> <div>
<div class="label"><span>Server-Content</span><span id="s-pct">0%</span></div> <div class="label"><span>Server-Content</span><span id="s-pct">0%</span></div>
<div class="bar" id="s-bar"><div class="fill" id="s-fill"></div></div> <div class="bar" id="s-bar"><div class="fill" id="s-fill"></div></div>
<div class="sub" id="s-sub"></div> <div class="sub" id="s-sub">Warte auf Daten …</div>
</div> </div>
<!-- Init -->
<div> <div>
<div class="label"><span>Initialisierung</span><span id="i-pct"></span></div> <div class="label"><span>Initialisierung</span><span id="i-pct"></span></div>
<div class="bar indet" id="i-bar"></div> <div class="bar indet" id="i-bar"></div>
@@ -103,6 +107,7 @@ a{color:var(--accent); text-decoration:none}
<div class="small" style="margin-top:10px">Datei: <span id="file"></span></div> <div class="small" style="margin-top:10px">Datei: <span id="file"></span></div>
</div> </div>
<!-- Tipps / Links -->
<div class="box"> <div class="box">
<strong>Tipps</strong> <strong>Tipps</strong>
<ul class="tip" style="margin:10px 0 0 18px"> <ul class="tip" style="margin:10px 0 0 18px">
@@ -111,7 +116,7 @@ a{color:var(--accent); text-decoration:none}
<li><span class="kbd">Tab</span> zeigt die Rollen-Übersicht.</li> <li><span class="kbd">Tab</span> zeigt die Rollen-Übersicht.</li>
</ul> </ul>
<div class="footer"> <div class="footer">
<span>Website: <a href="https://ttt.dk0.dev">ttt.dk0.dev</a></span> <span>Website: <a href="https://ttt.dk0.dev" target="_blank" rel="noreferrer">ttt.dk0.dev</a></span>
</div> </div>
</div> </div>
</div> </div>
@@ -121,65 +126,73 @@ a{color:var(--accent); text-decoration:none}
<script> <script>
(() => { (() => {
const $ = id => document.getElementById(id); const $ = id => document.getElementById(id);
// Grunddaten aus URL (GMod hängt ?map=%m&sid=%s an)
const p = new URLSearchParams(location.search);
$('map').textContent = p.get('map') || 'Unbekannt';
$('me').textContent = p.get('sid') || '—';
// Phasen-State // Elemente
const phases = { const ph = { work:$('ph-work'), srv:$('ph-srv'), init:$('ph-init') };
work: { elFill: $('w-fill'), elPct: $('w-pct'), elSub: $('w-sub'), elStep: $('st-work'), const work = { fill:$('w-fill'), pct:$('w-pct'), sub:$('w-sub'), total:0, need:0, last:0, done:false };
total:0, needed:0, last:0, done:false }, const srv = { fill:$('s-fill'), pct:$('s-pct'), sub:$('s-sub'), total:0, need:0, last:0, done:false };
srv: { elFill: $('s-fill'), elPct: $('s-pct'), elSub: $('s-sub'), elStep: $('st-srv'), const init = { pct:$('i-pct'), sub:$('i-sub') };
total:0, needed:0, last:0, done:false }, let phase = 'work';
init: { elBar: $('i-bar'), elPct: $('i-pct'), elSub: $('i-sub'), elStep: $('st-init'),
spin:true, done:false }
};
let current = 'work'; // heuristisch anfangen bei Workshop
function setActive(key){ function setPhase(next){
['work','srv','init'].forEach(k=>{ phase = next;
const c = phases[k].elStep.classList; // Chips visuell updaten
c.remove('active'); c.remove('done'); [ph.work, ph.srv, ph.init].forEach(el => el.classList.remove('on'));
if (phases[k].done) c.add('done'); if (next === 'work') ph.work.classList.add('on');
}); if (next === 'srv') ph.srv.classList.add('on');
phases[key].elStep.classList.add('active'); if (next === 'init') ph.init.classList.add('on');
current = key;
} }
function setPct(obj, pct, label){ function setPct(obj, v){
pct = Math.max(obj.last||0, Math.min(100, Math.round(pct))); v = Math.max(obj.last || 0, Math.min(100, Math.round(v)));
obj.last = pct; obj.last = v;
obj.elFill.style.width = pct + '%'; obj.fill.style.width = v + '%';
obj.elPct.textContent = pct + '%'; obj.pct.textContent = v + '%';
if (label) obj.elSub.textContent = label;
if (pct >= 100){ obj.done = true; obj.elStep.classList.add('done'); }
} }
// ===== GMod Callbacks ===== function updateBars(){
window.GameDetails = function(serverName, serverUrl, mapName, maxPlayers, steamID, gamemode){ // Workshop
if (work.total > 0){
const done = Math.max(0, work.total - (work.need||0));
setPct(work, (done / work.total) * 100);
work.sub.textContent = `${done} / ${work.total} Dateien`;
if (!work.done && done >= work.total){ work.done = true; setPhase('srv'); }
}
// Server
if (srv.total > 0){
const done = Math.max(0, srv.total - (srv.need||0));
setPct(srv, (done / srv.total) * 100);
srv.sub.textContent = `${done} / ${srv.total} Dateien`;
if (!srv.done && done >= srv.total){ srv.done = true; setPhase('init'); }
}
}
// ===== von GMod aufgerufene Funktionen =====
window.GameDetails = function(serverName, serverURL, mapName, maxPlayers, steamID, gamemode){
$('sv').textContent = serverName || 'Unbekannt'; $('sv').textContent = serverName || 'Unbekannt';
$('sv2').textContent = serverName || 'deinem Server'; $('sv2').textContent = serverName || 'deinem Server';
$('map').textContent = mapName || 'Unbekannt';
$('gm').textContent = gamemode || 'TTT2'; $('gm').textContent = gamemode || 'TTT2';
if (mapName) $('map').textContent = mapName; $('me').textContent = steamID || '—';
if (steamID) $('me').textContent = steamID;
}; };
// GMod ruft diese zweimal in „Wellen“ auf: erst Workshop, dann Server-DLs.
window.SetFilesTotal = function(total){ window.SetFilesTotal = function(total){
total = Number(total) || 0; total = Number(total) || 0;
// Heuristik: wenn innerhalb Workshop der Zähler „neu startet“ (kleiner wird), beginnt oft der Server-Content // Heuristik: Wenn im Workshop die Gesamtzahl plötzlich kleiner wird, dann startet Server-Content
const phase = phases[current]; if (phase === 'work' && work.total && total && total < work.total && (work.last >= 95 || work.done)){
if (current === 'work' && phase.total && total > 0 && total < phase.total && phase.last >= 95){ work.done = true; setPhase('srv');
phases.work.done = true; phases.work.elStep.classList.add('done');
setActive('srv');
} }
phases[current].total = total; if (phase === 'work'){ work.total = total; }
update(); else if (phase === 'srv'){ srv.total = total; }
updateBars();
}; };
window.SetFilesNeeded = function(needed){ window.SetFilesNeeded = function(need){
phases[current].needed = Number(needed) || 0; need = Number(need) || 0;
update(); if (phase === 'work'){ work.need = need; }
else if (phase === 'srv'){ srv.need = need; }
updateBars();
}; };
window.DownloadingFile = function(path){ window.DownloadingFile = function(path){
@@ -187,57 +200,30 @@ a{color:var(--accent); text-decoration:none}
}; };
window.SetStatusChanged = function(status){ window.SetStatusChanged = function(status){
const s = (status||'').toLowerCase(); const s = (status || '').toLowerCase();
// Mapping von Status auf Phasen
if (s.includes('workshop')) setActive('work');
else if (s.includes('sending client info') || s.includes('precaching') || s.includes('client info') || s.includes('parsing')) {
phases.work.done && (phases.work.elStep.classList.add('done'));
phases.srv.done && (phases.srv.elStep.classList.add('done'));
setActive('init');
phases.init.elPct.textContent = '…';
phases.init.elSub.textContent = status || 'Initialisiere …';
}
else if (s.includes('download') || s.includes('materials') || s.includes('models') || s.includes('.bsp') || s.includes('file')) {
phases.work.done && (phases.work.elStep.classList.add('done'));
setActive('srv');
}
// „Workshop complete“ → zur nächsten Phase // Phasenwechsel anhand Status
if (s.includes('complete') && current === 'work'){ if (s.includes('workshop')) setPhase('work');
phases.work.done = true; setPct(phases.work, 100, 'Workshop abgeschlossen'); if (s.includes('download') || s.includes('materials') || s.includes('models') || s.includes('.bsp')) setPhase('srv');
setActive('srv'); if (s.includes('sending client info') || s.includes('precaching') || s.includes('client info') || s.includes('spawn'))
} setPhase('init');
// hübscher Status-Text // Untertitel je nach Phase
if (current === 'work') phases.work.elSub.textContent = status || ''; if (phase === 'work') work.sub.textContent = status || '';
else if (current === 'srv') phases.srv.elSub.textContent = status || ''; if (phase === 'srv') srv.sub.textContent = status || '';
else phases.init.elSub.textContent = status || ''; if (phase === 'init'){ init.sub.textContent = status || 'Initialisiere …'; init.pct.textContent = '…'; }
}; };
function update(){ // Kleiner Fallback fürs Testen im normalen Browser (kein Muss)
// Workshop/Srv: determiniert falls Total > 0 if (!('GameDetails' in window)) {
['work','srv'].forEach(k=>{ // Dummy-Werte
const ph = phases[k]; window.GameDetails('Gaming Mäuse - TTT', '', 'ttt_lego', 16, 'STEAM_0:1:123456', 'terrortown');
if (ph.total > 0){ // Fake-Progress
const done = Math.max(0, ph.total - (ph.needed||0)); let a=0,b=0; const fake = setInterval(()=>{
const pct = (done / ph.total) * 100; if (a < 100){ work.total=100; work.need=100-a; a+=7; updateBars(); }
setPct(ph, pct, ph.total? `${done} / ${ph.total} Dateien` : ''); else if (b < 100){ setPhase('srv'); srv.total=100; srv.need=100-b; b+=12; updateBars(); }
}else{ else { setPhase('init'); clearInterval(fake); }
// Keine Zahlen → Indeterminate (lassen Balken einfach leer) }, 500);
ph.elPct.textContent = ph.done ? '100%' : '…';
if (!ph.done && ph.elSub.textContent === '') ph.elSub.textContent = 'Warte auf Daten …';
}
});
}
// Fallback-Animation im normalen Browser (ohne GMod)
if (document.visibilityState !== 'hidden') {
let t = 0, u = 0;
const f = setInterval(()=>{
if (phases.init.done) { clearInterval(f); return; }
if (!phases.work.done) { t = Math.min(100, t + Math.random()*8); setPct(phases.work, t); if (t>=100){ setActive('srv'); } }
else if (!phases.srv.done) { u = Math.min(100, u + Math.random()*10); setPct(phases.srv, u); if (u>=100){ setActive('init'); } }
}, 1200);
} }
})(); })();
</script> </script>