WELTKAMPFOrbital Command · Live
Kein Reich 0 Reiche  online Modul 1
Ziehen zum Schwenken · Mausrad zoomt · Auf ein Feld klicken
Würfel-Schlacht · Spieleransicht

Auswertung

Jeder live anwesende Kämpfer würfelt 1×W6. Du siehst die Gesamtsummen beider Seiten. Verteidiger gewinnt bei Gleichstand.

⚔️ Du (Angreifer)

Live-Kämpfer
4

🛡️ Verteidiger

Live-Kämpfer
5

Offline-Stationierte halten mit, würfeln aber nicht. Bei Gleichstand gewinnt der Verteidiger.

Weltlage
Angriff planen

Zone angreifen

Lege fest, wann der Angriff ausgewertet wird — so versammelst du deine Mitkämpfer. Ein gestarteter Angriff kann nicht abgebrochen werden.

5
Minuten ab jetzt
Auswertung: —
Mitglieder

Echte Menschen, kein Knopfdruck

Mitglieder sind echte Spieler, die deinem Reich beitreten — über deinen persönlichen Einladungs-Link. Mehr Mitglieder = mehr Land, das du halten kannst, und mehr Würfel in der Schlacht. Beitritte sind erst mit dem Online-Modul aktiv — bis dahin bist du allein.

Nur zum Testen der Mechanik (Land-Limit & Würfel): Mitgliederzahl simulieren — im echten Spiel passiert das durch echte Beitritte.

Mitglieder (Demo)
1
direkt vor dem Body-Ende * in deine index.html (im /WeltSpiel/) einfügen. Sonst nichts ändern. * Danach in sw.js die Cache-Version auf wk-v22 hochsetzen und neu hochladen. * * Das Modul bringt eigenes UI mit (Dock unten rechts: ⚔️ Kämpfe / 👑 Feldherr), * pollt selbst /wk/battles + /wk/feldherr, spielt das 3×3 und sendet die Scores. * Es greift NICHT in dein bestehendes UI ein. * ========================================================================== */ (function () { 'use strict'; /* ===== CONFIG ============================================================ */ var CFG = { API: 'https://wk.nexthit.de', POLL_MS: 15000, // Holt den Login-Token. Dein Login speichert ihn irgendwo – hier wird in // den üblichen Schlüsseln gesucht. Falls dein Code ihn anders ablegt: // einfach unten die richtige Zeile ergänzen (z.B. localStorage.getItem('DEIN_KEY')). getSession: function () { return window.WK_SESSION || localStorage.getItem('wk_session') || localStorage.getItem('wkSession') || localStorage.getItem('wk-session') || localStorage.getItem('session') || sessionStorage.getItem('wk_session') || ''; }, // Tuning des 3×3-Spiels (Balance): BATTLE: { grid:3, startLen:1, showMs:600, gapMs:250, speedup:0.93, minMs:260, maxRounds:25 } }; /* ===== API-Helfer ======================================================== */ function api(path, opts) { opts = opts || {}; var h = { 'Content-Type': 'application/json' }; var s = CFG.getSession(); if (s) h['X-WK-Session'] = s; return fetch(CFG.API + path, { method: opts.method || 'GET', headers: h, body: opts.body ? JSON.stringify(opts.body) : undefined }).then(function (r) { return r.json().then(function (j) { return { ok: r.ok, status: r.status, data: j }; }) .catch(function () { return { ok: r.ok, status: r.status, data: {} }; }); }); } /* ===== Styles ============================================================ */ (function injectCSS() { if (document.getElementById('wkc-style')) return; var css = '' + '.wkc-dock{position:fixed;right:14px;bottom:14px;display:flex;flex-direction:column;gap:10px;z-index:99990;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif}' + '.wkc-fab{position:relative;width:54px;height:54px;border-radius:50%;border:0;cursor:pointer;font-size:24px;color:#fff;box-shadow:0 6px 18px rgba(0,0,0,.4)}' + '.wkc-fab.att{background:#e63946}.wkc-fab.fh{background:#f0a500}.wkc-fab.dash{background:#2e8b6f}' + '.wkc-stats{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin:6px 0 4px}' + '.wkc-tile{background:#0d1117;border:1px solid #2a3038;border-radius:10px;padding:9px 11px}' + '.wkc-tile .v{font-size:20px;font-weight:800;font-variant-numeric:tabular-nums}' + '.wkc-tile .k{font-size:11px;color:#8b949e;text-transform:uppercase;letter-spacing:.3px}' + '.wkc-tile.wide{grid-column:1/3}' + '.wkc-fab[hidden]{display:none}' + '.wkc-badge{position:absolute;top:-4px;right:-4px;min-width:20px;height:20px;padding:0 5px;border-radius:10px;background:#111;color:#fff;font-size:12px;font-weight:700;line-height:20px;text-align:center;border:2px solid #fff}' + '.wkc-badge[hidden]{display:none}' + '.wkc-panel{position:fixed;right:14px;bottom:80px;width:min(340px,92vw);max-height:70vh;overflow:auto;background:#161b22;color:#e6edf3;border:1px solid #2a3038;border-radius:14px;padding:14px;z-index:99991;box-shadow:0 10px 30px rgba(0,0,0,.5);display:none}' + '.wkc-panel.on{display:block}' + '.wkc-h{font-size:15px;font-weight:800;margin:0 0 10px;display:flex;justify-content:space-between;align-items:center}' + '.wkc-x{background:none;border:0;color:#8b949e;font-size:20px;cursor:pointer}' + '.wkc-row{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:9px 0;border-top:1px solid #2a3038}' + '.wkc-row:first-of-type{border-top:0}' + '.wkc-name{font-size:14px}.wkc-sub{font-size:12px;color:#8b949e}' + '.wkc-b{border:0;border-radius:9px;padding:8px 13px;font-weight:700;color:#fff;cursor:pointer;font-size:13px}' + '.wkc-b.att{background:#e63946}.wkc-b.def{background:#3a86ff}.wkc-b.fh{background:#f0a500;color:#241a00}.wkc-b.ghost{background:transparent;border:1px solid #2a3038;color:#e6edf3}' + '.wkc-empty{color:#8b949e;font-size:13px;padding:8px 0}' + '.wkc-pts{font-weight:800;font-variant-numeric:tabular-nums}' + '.wkc-tag{font-size:11px;padding:2px 6px;border-radius:6px;margin-left:6px}' + '.wkc-tag.fh{background:#f0a500;color:#241a00}.wkc-tag.cand{background:#2a3038;color:#e6edf3}' + '.wkc-banner{background:#f0a500;color:#241a00;border-radius:10px;padding:9px 11px;font-weight:700;font-size:13px;margin-bottom:10px}' + '.wkc-toast{position:fixed;left:50%;bottom:90px;transform:translateX(-50%);background:#111;color:#fff;padding:10px 16px;border-radius:10px;z-index:99999;font-size:14px;box-shadow:0 6px 18px rgba(0,0,0,.4);opacity:0;transition:opacity .2s}' + '.wkc-toast.on{opacity:1}' /* ---- 3×3 Kampf-Overlay ---- */ + '.wkb-ov{position:fixed;inset:0;background:rgba(3,6,10,.93);display:none;align-items:center;justify-content:center;z-index:99998;padding:14px}' + '.wkb-ov.on{display:flex}' + '.wkb-card{width:min(420px,96vw);background:#161b22;border:2px solid #e63946;border-radius:16px;padding:18px;text-align:center;color:#e6edf3;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif}' + '.wkb-who{font-size:12px;color:#8b949e;letter-spacing:.5px;text-transform:uppercase}' + '.wkb-title{font-size:18px;font-weight:800;margin:2px 0 10px}' + '.wkb-hud{display:flex;justify-content:center;gap:22px;margin-bottom:8px;font-variant-numeric:tabular-nums}' + '.wkb-hud .k{font-size:11px;color:#8b949e;text-transform:uppercase}.wkb-hud .v{font-size:20px;font-weight:800}' + '.wkb-status{min-height:22px;font-size:14px;color:#8b949e;margin-bottom:10px}' + '.wkb-status.watch{color:#3a86ff}.wkb-status.input{color:#2ecc71}' + '.wkb-board{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;max-width:280px;margin:0 auto 14px}' + '.wkb-cell{border:1px solid #2a3038;background:#1f2630;border-radius:10px;width:100%;aspect-ratio:1/1;transition:background .08s}' + '.wkb-cell.lit{background:#fff}.wkb-cell.ok{background:#2ecc71}.wkb-cell.bad{background:#e63946}' + '.wkb-board.shake{animation:wkbShake .4s}' + '@keyframes wkbShake{0%,100%{transform:translateX(0)}20%,60%{transform:translateX(-7px)}40%,80%{transform:translateX(7px)}}' + '.wkb-start{background:#e63946;color:#fff;border:0;border-radius:10px;width:100%;padding:13px;font-size:15px;font-weight:700;cursor:pointer}' + '.wkb-done{font-size:15px;margin:4px 0 12px}.wkb-done b{font-size:30px;display:block;margin-top:4px}' + '@media (prefers-reduced-motion:reduce){.wkb-board.shake{animation:none}.wkb-cell{transition:none}}'; var st = document.createElement('style'); st.id = 'wkc-style'; st.textContent = css; document.head.appendChild(st); })(); /* ===== 3×3-Engine (WKBattle) ============================================ */ var WKBattle = (function () { var C = CFG.BATTLE; function Snd(){ this.ctx=null; this.on=true; } Snd.prototype.ensure=function(){ if(!this.ctx){var A=window.AudioContext||window.webkitAudioContext; if(A)this.ctx=new A();} if(this.ctx&&this.ctx.state==='suspended')this.ctx.resume(); }; Snd.prototype.tone=function(f,d,t,v){ if(!this.on)return; this.ensure(); if(!this.ctx)return; var c=this.ctx.currentTime,o=this.ctx.createOscillator(),g=this.ctx.createGain(); o.type=t||'sine'; o.frequency.setValueAtTime(f,c); g.gain.setValueAtTime(0.0001,c); g.gain.exponentialRampToValueAtTime(v||0.18,c+0.01); g.gain.exponentialRampToValueAtTime(0.0001,c+(d||0.18)); o.connect(g).connect(this.ctx.destination); o.start(c); o.stop(c+(d||0.18)+0.02); }; Snd.prototype.lit=function(i){ this.tone(330+(i%3)*90+Math.floor(i/3)*40,0.22,'triangle',0.16); }; Snd.prototype.click=function(i){ this.tone(360+(i%3)*100,0.16,'sine',0.18); }; Snd.prototype.good=function(){ var s=this; this.tone(660,0.12,'square',0.14); setTimeout(function(){s.tone(880,0.16,'square',0.14);},120); }; Snd.prototype.err=function(){ var s=this; this.tone(160,0.12,'sawtooth',0.2); setTimeout(function(){s.tone(110,0.35,'sawtooth',0.2);},110); }; Snd.prototype.start=function(){ var s=this; this.tone(440,0.12,'sine',0.15); setTimeout(function(){s.tone(660,0.16,'sine',0.15);},120); }; var ov=null; function build(){ if(ov) return ov; ov=document.createElement('div'); ov.className='wkb-ov'; ov.innerHTML='
' +'
Kampf · 3×3
' +'
Runde
0
' +'
Punkte
0
' +'
Bereit?
' +'
' +'' +'
'; document.body.appendChild(ov); return ov; } function play(opts){ opts=opts||{}; ov=build(); var card=ov.querySelector('.wkb-card'), board=ov.querySelector('[data-board]'), roundEl=ov.querySelector('[data-round]'), scoreEl=ov.querySelector('[data-score]'), statusEl=ov.querySelector('[data-status]'), whoEl=ov.querySelector('[data-who]'), doneEl=ov.querySelector('[data-done]'), startBtn=ov.querySelector('[data-start]'); var snd=new Snd(); whoEl.textContent=opts.label||''; var col = opts.side==='def' ? '#3a86ff' : '#e63946'; card.style.borderColor=col; startBtn.style.background=col; var N=C.grid*C.grid, cells=[], i; board.innerHTML=''; for(i=0;iC.maxRounds){ finish(); return; } playSeq(); } function award(){ score+=round*10+streak*2; scoreEl.textContent=score; } function handle(idx,cell){ if(!accept)return; if(idx===seq[inIdx]){ snd.click(idx); cell.classList.add('ok'); later(function(){cell.classList.remove('ok');},300); inIdx++; if(inIdx>=seq.length){ accept=false; award(); snd.good(); streak++; setStatus('Runde '+round+' ✓'); later(function(){nextRound(false);},780); } } else { accept=false; snd.err(); cell.classList.add('bad'); board.classList.add('shake'); later(function(){ cell.classList.remove('bad'); board.classList.remove('shake'); finish(); },520); } } board.onclick=function(e){ var c=e.target.closest('.wkb-cell'); if(!c)return; handle(+c.dataset.i,c); }; function finish(){ if(finished)return; finished=true; accept=false; clearAll(); setStatus('Kampf beendet'); doneEl.innerHTML='Kampfkraft:'+score+''; doneEl.hidden=false; startBtn.disabled=false; startBtn.textContent='Übernehmen'; startBtn.onclick=function(){ ov.classList.remove('on'); if(opts.onFinish)opts.onFinish({score:score}); }; } function begin(){ snd.ensure(); reset(); snd.start(); setStatus('Bereit machen …'); startBtn.disabled=true; startBtn.textContent='…'; later(function(){ for(var k=0;k' +'
Feld '+esc(b.target)+' · '+countdown(b.resolveAt)+'
'; var btn=el('button','wkc-b '+(b.side==='att'?'att':'def'),'Spielen'); btn.onclick=function(){ startBattle(b, cm, btn); }; row.appendChild(left); row.appendChild(btn); panelAtt.appendChild(row); }); }); if(!any) panelAtt.appendChild(el('div','wkc-empty','Gerade keine Kämpfe. Stationiere Kommandanten an der Front und warte auf einen geplanten Angriff.')); } function startBattle(b, cm, btn){ gameOpen=true; if(btn)btn.disabled=true; WKBattle.play({ label:(b.side==='att'?'Angriff':'Verteidigung')+' · Feld '+b.target, side:b.side, onFinish:function(r){ api('/wk/battle/score',{method:'POST',body:{attack_id:b.attack_id,commander_id:cm.commander_id,score:r.score}}) .then(function(res){ gameOpen=false; if(res.ok && res.data && res.data.ok){ toast('Kampfkraft '+r.score+' übermittelt ⚔️'); } else if(res.status===409){ toast('Bereits gespielt.'); } else { toast((res.data && res.data.error) || 'Fehler beim Senden'); if(btn)btn.disabled=false; } refreshBattles(); }).catch(function(){ gameOpen=false; toast('Verbindungsfehler'); if(btn)btn.disabled=false; }); } }); } /* ===== Feldherr / Ehrentafel ============================================ */ function loadFeldherr(){ panelFh.innerHTML='
👑 Feldherr
Lade …
'; panelFh.querySelector('.wkc-x').onclick=function(){ panelFh.classList.remove('on'); }; Promise.all([api('/wk/feldherr'), api('/wk/ehrentafel')]).then(function(rr){ var f=rr[0].data||{}, e=rr[1].data||{}; if(!rr[0].ok || !f.ok){ renderFhError(f.error||'Kein Reich'); return; } renderFeldherr(f, e); }).catch(function(){ renderFhError('Verbindungsfehler'); }); } function renderFhError(msg){ panelFh.innerHTML=''; var h=el('div','wkc-h',''); h.innerHTML='👑 Feldherr'; h.querySelector('.wkc-x').onclick=function(){ panelFh.classList.remove('on'); }; panelFh.appendChild(h); panelFh.appendChild(el('div','wkc-empty',msg)); } function renderFeldherr(f, e){ panelFh.innerHTML=''; var h=el('div','wkc-h',''); h.innerHTML='👑 Feldherr'; h.querySelector('.wkc-x').onclick=function(){ panelFh.classList.remove('on'); }; panelFh.appendChild(h); if(f.announcement){ panelFh.appendChild(el('div','wkc-banner','👑 Neuer Feldherr: '+esc(f.announcement.name))); } var top=el('div','',''); top.innerHTML='
Amtierend: '+(f.feldherr?esc(f.feldherr.name):'—')+'' +' (Faktor ×'+(Math.round((f.factor||1)*100)/100)+')
' +'
Nächster Wechsel in '+periodTxt(f.periodEndSec)+' · Periode '+(f.periodMonths||1)+' Mon.
'; panelFh.appendChild(top); var note=el('div','wkc-sub','Wer zum Monatsende die meisten Saisonpunkte hat, wird automatisch Feldherr. Spiel deine Kämpfe, um aufzusteigen.'); note.style.margin='10px 0'; panelFh.appendChild(note); panelFh.appendChild(el('div','wkc-sub','Ehrentafel (Saisonpunkte, intern):')); var members=(e&&e.members)||[]; if(!members.length){ panelFh.appendChild(el('div','wkc-empty','Noch keine Punkte in dieser Saison.')); return; } members.forEach(function(m,i){ var row=el('div','wkc-row',''); var left=el('div','',''); var tags=''; if(m.isFeldherr)tags+='Feldherr'; else if(m.isCandidate)tags+='Kandidat'; left.innerHTML='
'+(i+1)+'. '+esc(m.name)+tags+'
'; row.appendChild(left); row.appendChild(el('div','wkc-pts',String(m.points))); panelFh.appendChild(row); }); } /* ===== Spieler-Dashboard ================================================= */ function loadDashboard(){ panelDash.innerHTML='
📊 Mein Dashboard
Lade …
'; panelDash.querySelector('.wkc-x').onclick=function(){ panelDash.classList.remove('on'); }; api('/wk/dashboard').then(function(res){ if(!res.ok || !res.data || !res.data.ok){ renderDashError((res.data&&res.data.error)||'Kein Reich'); return; } renderDashboard(res.data); }).catch(function(){ renderDashError('Verbindungsfehler'); }); } function renderDashError(msg){ panelDash.innerHTML=''; var h=el('div','wkc-h',''); h.innerHTML='📊 Mein Dashboard'; h.querySelector('.wkc-x').onclick=function(){ panelDash.classList.remove('on'); }; panelDash.appendChild(h); panelDash.appendChild(el('div','wkc-empty',msg)); } function tile(k,v,wide){ var t=el('div','wkc-tile'+(wide?' wide':'')); t.innerHTML='
'+v+'
'+esc(k)+'
'; return t; } function renderDashboard(d){ panelDash.innerHTML=''; var h=el('div','wkc-h',''); h.innerHTML='📊 '+esc(d.name||'Dashboard')+''; h.querySelector('.wkc-x').onclick=function(){ panelDash.classList.remove('on'); }; panelDash.appendChild(h); if(d.isFeldherr) panelDash.appendChild(el('div','wkc-banner','👑 Du bist aktuell Feldherr von '+esc(d.realm||'')+' (Faktor ×'+(Math.round((d.factor||1)*100)/100)+')')); var g=el('div','wkc-stats'); g.appendChild(tile('Saisonpunkte', d.seasonPoints||0)); g.appendChild(tile('Saison-Rang', (d.seasonRank?('#'+d.seasonRank):'—')+' / '+(d.memberCount||1))); g.appendChild(tile('Kämpfe', d.battles||0)); g.appendChild(tile('Siegquote', (d.winRate||0)+'%')); g.appendChild(tile('Siege', d.wins||0)); g.appendChild(tile('Niederlagen', d.losses||0)); g.appendChild(tile('Angriffe', d.attacks||0)); g.appendChild(tile('Verteidigungen', d.defenses||0)); g.appendChild(tile('Bester Score', d.best||0)); g.appendChild(tile('Gesamtpunkte', d.totalPoints||0)); g.appendChild(tile('Deserteur ('+(d.desertionRate||0)+'%)', d.desertions||0, true)); panelDash.appendChild(g); panelDash.appendChild(el('div','wkc-sub','Nächste Feldherr-Wahl in '+periodTxt(d.periodEndSec)+'.')); } /* ===== Helfer ============================================================ */ function esc(s){ var d=document.createElement('div'); d.textContent=s==null?'':String(s); return d.innerHTML; } function countdown(ms){ var s=Math.max(0,Math.round((ms-Date.now())/1000)); var m=Math.floor(s/60); return s<60?('noch '+s+'s'):('noch '+m+' Min'); } function periodTxt(sec){ if(sec==null)return '—'; var d=Math.floor(sec/86400), h=Math.floor((sec%86400)/3600); if(d>0)return d+' Tg '+h+' Std'; var m=Math.floor((sec%3600)/60); return h>0?(h+' Std'):(m+' Min'); } /* ===== Boot ============================================================== */ function boot(){ buildDock(); refreshBattles(); setInterval(refreshBattles, CFG.POLL_MS); // Feldherr-Ankündigung im Hintergrund prüfen, damit das Banner auch ohne // offenes Panel kommt (1×/Minute reicht). setInterval(function(){ if(!CFG.getSession())return; api('/wk/feldherr').then(function(res){ var f=res.data; if(f&&f.ok&&f.announcement){ toast('👑 Neuer Feldherr: '+f.announcement.name); } }).catch(function(){}); }, 60000); } if(document.readyState==='loading') document.addEventListener('DOMContentLoaded', boot); else boot(); })();