362 lines
17 KiB
JavaScript
362 lines
17 KiB
JavaScript
/* 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!');
|
||
}
|
||
}
|
||
|