edu-boardgame-generator/js/share.js

362 lines
17 KiB
JavaScript
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.

/* share.js — Veröffentlichen: Link/QR/PDF, Teilen */
/* ══════════════════════════════════════════════
SUMMARY CARD
══════════════════════════════════════════════ */
function showSummaryCard(){
document.getElementById('fbCard').style.display='none';
const sc=document.getElementById('sumCard');sc.style.display='block';
sc.style.animation='slideIn 0.4s ease both';
setTimeout(()=>sc.scrollIntoView({behavior:'smooth',block:'start'}),400);
const fun=fbAnswers.fun||3;const mg=fbAnswers.minigames||3;
const diff=fbAnswers.difficulty;const replay=fbAnswers.replay;
document.getElementById('sumText').textContent='Du hast dein Spiel getestet und Feedback gegeben. Hier ist deine Auswertung:';
document.getElementById('sumScores').innerHTML=`<div class="sum-sc"><div class="sum-val">${fun}/5</div><div class="sum-lbl">Spaßfaktor</div></div><div class="sum-sc"><div class="sum-val">${mg}/5</div><div class="sum-lbl">Mini-Game Passung</div></div><div class="sum-sc"><div class="sum-val">${ST.fieldCount}</div><div class="sum-lbl">Felder</div></div><div class="sum-sc"><div class="sum-val">${ST.storyItems.length}</div><div class="sum-lbl">Erzähltexte</div></div>`;
const notes=[];
if(fun<=2)notes.push({i:'🎯',t:'Der Spaßfaktor ist noch niedrig — überlege, spannendere Mini-Games oder Erzähltexte hinzuzufügen.'});
else if(fun>=4)notes.push({i:'🔥',t:'Super Spaßfaktor! Dein Spiel macht offensichtlich Freude.'});
if(diff===0)notes.push({i:'⚠️',t:'Das Spiel war zu einfach — probiere schwierigere Mini-Games oder weniger Leben.'});
else if(diff===3)notes.push({i:'⚠️',t:'Das Spiel war zu schwer — erhöhe die Lebensanzahl oder wähle einfachere Mini-Games.'});
if(ST.storyItems.length===0)notes.push({i:'📖',t:'Keine Erzähltexte vorhanden — sie machen das Spiel viel lebendiger!'});
if(ST.fields.slice(1,ST.fieldCount-1).filter(Boolean).length<3)notes.push({i:'🕹️',t:'Nur wenige Felder sind belegt — füge mehr Mini-Games hinzu für mehr Abwechslung.'});
if(replay>=2)notes.push({i:'🚀',t:'Das Spiel hat Wiederspielwert — großartig!'});
if(fbAnswers.problems&&fbAnswers.problems.length>5)notes.push({i:'🔧',t:`Problem: "${esc(fbAnswers.problems)}" — geh zurück und überarbeite das.`});
if(notes.length===0)notes.push({i:'✅',t:'Alles sieht gut aus! Dein Spiel ist bereit zum Teilen.'});
document.getElementById('sumNotes').innerHTML=notes.map(n=>`<div class="sn-item"><span>${n.i}</span><span>${n.t}</span></div>`).join('');
save();localStorage.setItem('testFeedback',JSON.stringify(fbAnswers));
}
// ── URL-Sharing System ─────────────────────────────────────────────────────
// Komprimiert die Spielkonfiguration und kodiert sie als URL-Hash
// JSON → pako.gzip (DEFLATE) → Base64 → URL-Hash
function buildShareURL() {
// Nur die nötigen Felder exportieren (kein highestStep etc.)
const exportCfg = {
devName: ST.devName,
name: ST.name,
desc: ST.desc,
figure: ST.figure,
figure2: ST.figure2,
playerCount: ST.playerCount,
background: ST.background,
fieldCount: ST.fieldCount,
fields: ST.fields,
storyItems: ST.storyItems,
quizData: ST.quizData,
rules: ST.rules,
consequences:ST.consequences,
mgSettings: ST.mgSettings,
worldSeed: ST.worldSeed,
worldLocked: ST.worldLocked,
};
const json = JSON.stringify(exportCfg);
// pako ist über CDN geladen falls nicht verfügbar: direktes Base64
let encoded;
try {
const compressed = pako.deflate(json, { level: 9 });
// Uint8Array → binary string → btoa
const binary = Array.from(compressed).map(b => String.fromCharCode(b)).join('');
encoded = 'z:' + btoa(binary);
} catch(e) {
// Fallback ohne Kompression
encoded = 'j:' + btoa(unescape(encodeURIComponent(json)));
}
const base = window.location.href.replace(/\/editor\.html.*$/, '') + '/game.html';
return base + '#' + encoded;
}
// ── Kurz-Code (6 Zeichen) zum manuellen Eintippen ──
const CODE_ALPHA = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ'; // ohne 0,O,1,I,L (Verwechslungsschutz)
function genShareCode(){
let s = '';
for(let i=0;i<6;i++) s += CODE_ALPHA[Math.floor(Math.random()*CODE_ALPHA.length)];
return s;
}
// URL der /play/-Landing-Page (für den zweiten QR)
function playPageURL(){
return window.location.href.replace(/\/editor\.html.*$/, '') + '/play/';
}
// Spielkonfiguration zum Server hochladen → Kurz-Code reservieren
async function uploadCfgForCode(code, cfg){
const res = await fetch('/api/boardgame-play/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code, cfg }),
});
if(!res.ok) throw new Error('Server-Fehler ' + res.status);
return res.json();
}
function doPublish() {
const url = buildShareURL();
// Spiel-Info füllen
const bg = BACKGROUNDS.find(b => b.id === ST.background);
const fig = FIGURES.find(f => f.id === ST.figure);
const infoEl = document.getElementById('shareGameInfo');
infoEl.innerHTML = `
<div style="display:flex;gap:12px;align-items:center">
<div style="font-size:2rem">${fig ? fig.e : '🎮'}${bg ? bg.e : ''}</div>
<div>
<div style="font-family:'Fredoka One',cursive;font-size:1.1rem;color:#e2e0f0">${esc(ST.name || 'Mein Spiel')}</div>
<div style="font-size:12px;color:#a7a3c2">von ${esc(ST.devName || 'Anonym')} · ${ST.fieldCount} Felder · ${ST.fields.filter(Boolean).length} Mini-Games</div>
</div>
</div>
`;
// Link setzen
const input = document.getElementById('shareLinkInput');
input.value = url;
const lenEl = document.getElementById('shareLinkLen');
lenEl.textContent = `Link-Länge: ${url.length} Zeichen${url.length > 4000 ? ' ⚠️ zu lang für QR-Code!' : ' ✅ QR-Code-tauglich'}`;
// Haupt-QR-Code (zum direkten Spiel-Link)
const qrEl = document.getElementById('shareQR');
qrEl.innerHTML = '';
try {
new QRCode(qrEl, {
text: url, width: 180, height: 180,
colorDark: '#000000', colorLight: '#ffffff', correctLevel: QRCode.CorrectLevel.M,
});
} catch(e) {
qrEl.innerHTML = '<div style="color:#ef4444;font-size:12px;padding:20px">QR-Code konnte nicht erstellt werden.<br>Link zum Kopieren nutzen.</div>';
}
// Overlay zeigen
const ov = document.getElementById('shareOverlay');
ov.style.display = 'flex';
document.body.style.overflow = 'hidden';
// ── Kurz-Code-Bereich ──
const codeBox = document.getElementById('shareCodeBox');
const codeEl = document.getElementById('shareCodeText');
const playUrlEl= document.getElementById('sharePlayUrl');
const playQrEl = document.getElementById('sharePlayQR');
const codeStatus = document.getElementById('shareCodeStatus');
if(codeBox){
codeBox.style.display = '';
if(codeEl) codeEl.textContent = '······';
if(codeStatus) codeStatus.textContent = 'wird erzeugt …';
if(playUrlEl) playUrlEl.textContent = playPageURL().replace(/^https?:\/\//,'');
if(playQrEl){
playQrEl.innerHTML = '';
try{
new QRCode(playQrEl, { text: playPageURL(), width:120, height:120,
colorDark:'#000000', colorLight:'#ffffff', correctLevel: QRCode.CorrectLevel.M });
}catch(e){}
}
}
// Code reservieren (re-publish nutzt denselben Code)
const exportCfg = {
devName: ST.devName, name: ST.name, desc: ST.desc, figure: ST.figure, figure2: ST.figure2,
playerCount: ST.playerCount, background: ST.background, fieldCount: ST.fieldCount,
fields: ST.fields, storyItems: ST.storyItems, quizData: ST.quizData, rules: ST.rules,
consequences: ST.consequences,
mgSettings: ST.mgSettings, worldSeed: ST.worldSeed, worldLocked: ST.worldLocked,
};
const useCode = ST.shareCode || genShareCode();
uploadCfgForCode(useCode, exportCfg)
.then(() => {
ST.shareCode = useCode;
save();
if(codeEl) codeEl.textContent = useCode;
if(codeStatus) codeStatus.textContent = '✓ bereit';
})
.catch(err => {
console.warn('Kurz-Code Upload fehlgeschlagen:', err);
if(codeEl) codeEl.textContent = '— —';
if(codeStatus) codeStatus.innerHTML = '<span style="color:#ef4444">offline?</span>';
});
}
function closeShare() {
document.getElementById('shareOverlay').style.display = 'none';
document.body.style.overflow = '';
}
async function printAsPDF() {
// Heutiges Datum DD.MM.YYYY (dynamisch — kein Hardcode)
const _d = new Date();
const todayStr = String(_d.getDate()).padStart(2,'0') + '.' +
String(_d.getMonth()+1).padStart(2,'0') + '.' + _d.getFullYear();
const qrEl = document.getElementById('shareQR');
const canvas = qrEl.querySelector('canvas');
if (!canvas) { appAlert('QR-Code nicht gefunden.'); return; }
const qrDataUrl = canvas.toDataURL('image/png');
const { jsPDF } = window.jspdf;
const doc = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' });
const pageW = 210, pageH = 297, margin = 14;
await new Promise(resolve => {
const img = new Image();
img.onload = () => {
const c = document.createElement('canvas');
c.width = img.naturalWidth; c.height = img.naturalHeight;
c.getContext('2d').drawImage(img, 0, 0);
// Logo klein, zentriert
const logoW = 72, logoH = logoW * (img.naturalHeight / img.naturalWidth);
doc.addImage(c.toDataURL('image/png'), 'PNG', (pageW - logoW) / 2, margin, logoW, logoH);
let y = margin + logoH + 3;
// Trennlinie grau
doc.setDrawColor(160, 160, 160); doc.setLineWidth(0.3);
doc.line(margin, y, pageW - margin, y); y += 4;
// Fach-Bezeichnung
doc.setFont('helvetica', 'normal'); doc.setFontSize(9); doc.setTextColor(90, 90, 90);
doc.text('Fach Informatik & Medien- und Bildungsmanagement', pageW / 2, y, { align: 'center' }); y += 5;
// Datum
doc.setFont('helvetica', 'bold'); doc.setFontSize(10); doc.setTextColor(40, 40, 40);
doc.text(todayStr, pageW / 2, y, { align: 'center' }); y += 6;
// Spielname
doc.setFont('helvetica', 'bold'); doc.setFontSize(22); doc.setTextColor(10, 10, 10);
doc.text(ST.name || 'Mein Spiel', pageW / 2, y, { align: 'center' }); y += 5;
// Autor + Info
doc.setFont('helvetica', 'normal'); doc.setFontSize(9); doc.setTextColor(110, 110, 110);
const miniCount = ST.fields.filter(Boolean).length;
doc.text('von ' + (ST.devName || 'Anonym') + ' · ' + ST.fieldCount + ' Felder · ' + miniCount + ' Mini-Games', pageW / 2, y, { align: 'center' }); y += 9;
// ── Zwei Spalten: Haupt-QR (Direktstart) links, Kurz-Code-Block rechts ──
const colGap = 8;
const qrLarge = 70;
const colW = (pageW - 2*margin - colGap) / 2;
const leftX = margin;
const rightX = margin + colW + colGap;
// ── LINKE SPALTE: QR direkt ──
// Überschrift links
doc.setFont('helvetica', 'bold'); doc.setFontSize(9); doc.setTextColor(40,40,40);
doc.text('📱 Mit Handy scannen', leftX + colW/2, y, { align: 'center' });
const qrX = leftX + (colW - qrLarge) / 2;
const qrY = y + 4;
doc.setDrawColor(180,180,180); doc.setLineWidth(0.25);
doc.rect(qrX - 1, qrY - 1, qrLarge + 2, qrLarge + 2);
doc.addImage(qrDataUrl, 'PNG', qrX, qrY, qrLarge, qrLarge);
doc.setFont('helvetica','italic'); doc.setFontSize(7.5); doc.setTextColor(120,120,120);
doc.text('Kamera → öffnet Spiel direkt', leftX + colW/2, qrY + qrLarge + 4, { align: 'center' });
// ── RECHTE SPALTE: Kurz-Code + Play-Seite-QR ──
const shareCode = (ST.shareCode || '······').toUpperCase();
const playUrl = playPageURL().replace(/^https?:\/\//, '');
doc.setFont('helvetica', 'bold'); doc.setFontSize(9); doc.setTextColor(40,40,40);
doc.text('⌨️ Am PC eintippen', rightX + colW/2, y, { align: 'center' });
// Kleiner QR für /play/ Seite
const playQrEl = document.getElementById('sharePlayQR');
const playQrCanvas = playQrEl ? playQrEl.querySelector('canvas') : null;
const smallQR = 28;
if (playQrCanvas) {
const playQrDataUrl = playQrCanvas.toDataURL('image/png');
const playQrX = rightX + 2;
const playQrY = y + 4;
doc.setDrawColor(200,200,200); doc.setLineWidth(0.2);
doc.rect(playQrX - 0.5, playQrY - 0.5, smallQR + 1, smallQR + 1);
doc.addImage(playQrDataUrl, 'PNG', playQrX, playQrY, smallQR, smallQR);
doc.setFont('helvetica','normal'); doc.setFontSize(6); doc.setTextColor(120,120,120);
doc.text('zur Seite:', playQrX + smallQR/2, playQrY + smallQR + 3, { align: 'center' });
}
// URL-Text + Code
const txtX = rightX + smallQR + 8;
let codeY = y + 9;
doc.setFont('helvetica','normal'); doc.setFontSize(7); doc.setTextColor(80,80,80);
doc.text('1. Im Browser öffnen:', txtX, codeY); codeY += 3.5;
doc.setFont('helvetica','bold'); doc.setFontSize(8); doc.setTextColor(30,30,30);
doc.text(playUrl, txtX, codeY); codeY += 7;
doc.setFont('helvetica','normal'); doc.setFontSize(7); doc.setTextColor(80,80,80);
doc.text('2. Diesen Code eingeben:', txtX, codeY); codeY += 6;
doc.setFont('courier', 'bold'); doc.setFontSize(22); doc.setTextColor(20,20,20);
doc.text(shareCode, txtX, codeY);
y = qrY + qrLarge + 12;
// ---- Schnittlinie ----
const stripW = 43, stripH = 23;
const stripX = (pageW - stripW) / 2;
const stripY = pageH - margin - stripH;
const cutY = stripY - 6;
// Schnittlinie
doc.setDrawColor(150, 150, 150); doc.setLineWidth(0.25);
doc.setLineDashPattern([1.5, 1.5], 0);
doc.line(margin, cutY, pageW - margin, cutY);
doc.setLineDashPattern([], 0);
doc.setFont('helvetica', 'normal'); doc.setFontSize(5.5); doc.setTextColor(170, 170, 170);
doc.text('✂ Hier abschneiden', margin + 1, cutY - 1.2);
// ---- Ausschneidstreifen 41 × 23 mm (QR 23×23 | Text ~18×23) ----
// Rahmen
doc.setDrawColor(100, 100, 100); doc.setLineWidth(0.3);
doc.rect(stripX, stripY, stripW, stripH);
// Trennlinie bei 23mm (QR-Breite)
doc.setLineWidth(0.15); doc.setDrawColor(180,180,180);
doc.line(stripX + 23, stripY, stripX + 23, stripY + stripH);
// QR links 23×23
doc.addImage(qrDataUrl, 'PNG', stripX, stripY, 23, 23);
// Text rechts — linksbündig mit 1.5mm Einzug, vertikal zentriert
const tx = stripX + 23 + 1.5;
doc.setFont('helvetica', 'bold'); doc.setFontSize(5.5); doc.setTextColor(20, 20, 20);
const gameName = (ST.name || 'Mein Spiel').substring(0, 18);
doc.text(gameName, tx, stripY + 5);
doc.setFont('helvetica', 'normal'); doc.setFontSize(4); doc.setTextColor(110,110,110);
doc.text(todayStr + ' · PH Weingarten', tx, stripY + 9);
doc.setFont('helvetica', 'normal'); doc.setFontSize(4); doc.setTextColor(120,120,120);
doc.text('Code:', tx, stripY + 14);
doc.setFont('courier', 'bold'); doc.setFontSize(7); doc.setTextColor(20,20,20);
doc.text((ST.shareCode || '······').toUpperCase(), tx, stripY + 18.5);
resolve();
};
img.onerror = () => { resolve(); };
img.src = 'phw_logo.png';
});
const filename = (ST.name || 'spiel').replace(/[^a-zA-Z0-9_\-]/g, '_') + '_qr.pdf';
doc.save(filename);
}
function copyShareLink() {
const input = document.getElementById('shareLinkInput');
input.select();
navigator.clipboard.writeText(input.value).then(() => {
const btn = document.getElementById('shareCopyBtn');
btn.textContent = '✅ Kopiert!';
setTimeout(() => btn.textContent = '📋 Kopieren', 2000);
}).catch(() => {
document.execCommand('copy');
showToast('📋 Link kopiert!');
});
}
function shareViaEmail() {
const url = document.getElementById('shareLinkInput').value;
const subject = encodeURIComponent('🎲 Mein Brettspiel: ' + (ST.name || 'Mein Spiel'));
const body = encodeURIComponent(
'Hallo!\n\nIch habe ein Brettspiel erstellt und möchte es mit dir teilen:\n\n' +
(ST.name || 'Mein Spiel') + '\n' + url + '\n\nViel Spaß beim Spielen!\n' + (ST.devName || '')
);
window.open('mailto:?subject=' + subject + '&body=' + body);
}
function shareViaWhatsApp() {
const url = document.getElementById('shareLinkInput').value;
const text = encodeURIComponent('🎲 Mein Brettspiel "' + (ST.name || 'Mein Spiel') + '" einfach anklicken und spielen! ' + url);
window.open('https://wa.me/?text=' + text, '_blank', 'noopener,noreferrer');
}
function shareNative() {
const url = document.getElementById('shareLinkInput').value;
if (navigator.share) {
navigator.share({
title: ST.name || 'Mein Brettspiel',
text: 'Ich habe ein Brettspiel erstellt!',
url: url,
}).catch(() => {});
} else {
copyShareLink();
showToast('📋 Link in die Zwischenablage kopiert!');
}
}