Files
qivip/django/templates/play/game/score_overview.html
2025-06-08 19:51:10 +02:00

380 lines
15 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.

{% extends 'base.html' %}
{% block content %}
<div class="container mx-auto px-4">
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
<h1 class="text-3xl font-bold mb-6">Ergebnisse</h1>
<div class="mb-6">
<h2 class="text-xl font-bold mb-4">Aktuelle Frage</h2>
<p class="text-lg mb-2">{{ question_data.question }}</p>
<div class="grid grid-cols-2 gap-4">
{% for option in question_data.options %}
<div id="option-box-{{ forloop.counter0 }}" class="bg-gray-100 relative p-4 rounded-lg overflow-auto break-words hyphens-auto {% if option.is_correct %} border-2 border-green-500{% else %}border-2 border-red-500{% endif %}">
<div id="option-fill-{{ forloop.counter0 }}" class="absolute left-0 top-0 h-full {% if option.is_correct %}bg-green-300 {% else %}bg-red-300 {% endif %} opacity-50 z-0 transition-all duration-1500 " style="width: 0%;"></div>
<div class="relative z-10">
<p class="text-lg">{{ option.value }}</p>
<p class="text-gray-600" id="option-count-{{ forloop.counter0 }}">0 Antworten</p>
</div>
</div>
{% endfor %}
</div>
</div>
<div class="mb-6">
<h2 class="text-xl font-bold mb-4">Punktestand</h2>
<div id="scoreboard" class="leading-relaxed">
{% for participant in participants %}
{% if participant == my_participant or is_host %}
<div class="mt-2 shadow-sm p-4 bg-gray-100 rounded-lg border-2 border {% if forloop.counter <= 3 %}border-yellow-500{% else %}border-transparent{% endif %}">
<div class="flex flex-col items-start gap-1">
<div class="text-lg font-bold flex items-center gap-2">
{% if forloop.counter == 1 %}
🥇
{% elif forloop.counter == 2 %}
🥈
{% elif forloop.counter == 3 %}
🥉
{% else %}
{{ forloop.counter }}.
{% endif %}
<span class="display_name text-gray-600 font-black text-2xl " id="display_name-{{ forloop.counter0 }}">{{ participant.display_name }}</span>
</div class="flex flex-col items-start">
<p data-score="{{ participant.score }}" data-last-score="{{ participant.last_score }}" class="text-gray-600 font-bold show-score" id="score-{{ forloop.counter0 }}">{{ participant.score }} Punkte</p>
{% if participant == my_participant or request.session.mode == "learn"%}
<p class="text-blue-600 font-bold text-sm">+{{ participant.last_score }} Punkte</p>
{% endif %}
</div>
<script>
async function start() {
elements=document.getElementsByClassName("show-score");
for (let i = 0; i < elements.length; i++) {
const el = elements[i];
let counter=Number(el.getAttribute('data-score'))-Number(el.getAttribute('data-last-score'));
let goal=Number(el.getAttribute('data-score'));
let interval= setInterval(function(){
el.innerHTML = counter + " Punkte";
if(counter<=goal){
el.innerHTML = counter + " Punkte";
counter+=8;
}
else{
el.innerHTML = goal + " Punkte";
clearInterval(interval);
}
}, 20);}}
function bounce(){
const names = document.getElementsByClassName("display_name");
for (let i = 0; i < names.length; i++) {
names[i].classList.add("animate-bounce");
}
}
{% if is_last_question %}
bounce();
{% else %}
start();
{% endif %}
</script>
{% if participant == my_participant or request.session.mode == "learn" %}
{% if participant.last_answer_correct %}
<div class="mt-1 flex justify-center border-t-2 border-gray-300 gap-1">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="pt-2 text-center text-green-500 font-black size-10">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>
<div class=" pt-3 text-center text-gray-600 font-bold" id="antwort-richtig">
Hier erscheint gleich dein Feedback...
</div>
</div>
{% elif participant.last_answer_correct == False %}
<div class="mt-1 flex justify-center border-t-2 border-gray-300 gap-1">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="pt-2 text-center text-red-500 font-black size-10">
<path stroke-linecap="round" stroke-linejoin="round" d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>
<div class=" pt-3 text-center text-gray-600 font-bold" id="antwort-falsch">
Hier erscheint gleich dein Feedback...
</div>
<div class="flex justify-center border-t-2 border-gray-500 gap-1">
{% endif %}
{% endif %}
</div>
{% endif %}
{% endfor %}
</div>
</div>
{% if is_host %}
{% if is_last_question %}
<button id="finish-button" class="mt-4 w-full p-3 rounded-full bg-blue-600 text-white font-bold hover:bg-blue-700 transition-colors duration-200">
Quiz beenden
</button>
{% else %}
<button id="next-button" class="mt-4 w-full p-3 rounded-full bg-blue-600 text-white font-bold hover:bg-blue-700 transition-colors duration-200">
Nächste Frage
</button>
{% endif %}
{% endif %}
</div>
</div>
{% endblock %}
{% block extra_js %}
{% if is_last_question %}
<script>
// Hilfsfunktion für Pausen
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
document.addEventListener('DOMContentLoaded', async function() {
const items = document.querySelectorAll('#scoreboard div');
const top3 = Array.from(items).slice(0, 3); // Nur die ersten 3 animieren
// Sofort Startzustände für Top3 setzen, die anderen sofort sichtbar
items.forEach((el, index) => {
if (index >= 3) {
el.style.opacity = '1';
el.style.transform = 'translateY(0)';
} else {
el.style.opacity = '0';
el.style.transform = 'translateY(20px)';
}
});
try{
document.getElementById("finish-button").style.display = 'none';}
catch{}
// Warten bevor Animation startet
await sleep(500); // <<< hier die Pause
// Transition-Eigenschaft setzen nach Render-Zyklus
requestAnimationFrame(() => {
top3.forEach(el => {
el.style.transition = 'all 0.8s ease';
});
// Animation für Top 3 starten von Platz 3 zu 1
top3.reverse().forEach((el, index) => {
setTimeout(() => {
el.style.opacity = '1';
el.style.transform = 'translateY(0)';
if (index === top3.length - 1) {
triggerConfetti();
try{
document.getElementById("finish-button").style.display = 'block';}
catch{}
}
}, index * 1000); // Zeitversetzt aufdecken
});
});
});
function triggerConfetti() {
const end = Date.now() + 15 * 1000;
(function frame() {
confetti({ particleCount: 2, angle: 60, spread: 55, origin: { x: 0 } });
confetti({ particleCount: 2, angle: 120, spread: 55, origin: { x: 1 } });
if (Date.now() < end) requestAnimationFrame(frame);
})();
}
</script>
{% endif %}
{% include 'play/game/common_scripts.html' %}
{% if is_host %}
{% include 'play/game/sound.html' %}
{% endif %}
<script>
const richtigeAntworten = [
"Deine Antwort ist nicht falsch. Sie ist richtig!",
"Ja, du kannst es!",
"So kann es weiter gehen.",
"Ja, deine Antwort ist korrekt. Was soll ich dazu noch sagen? SUPER!",
"SUPER GUT.",
"Volltreffer!",
"Bravo! Das sitzt!",
"Na bitte, geht doch!",
"Schön. Einfach schön.",
"Exzellent kombiniert, Sherlock!",
"Punkt für dich!",
"Das war meisterhaft.",
"Korrekt - du scheinst das zu können!",
"So sieht Erfolg aus!",
"Glänzend gelöst!",
"Das war keine Glückssache - das war Können!",
"Treffer, versenkt!",
"Du hast es drauf!",
"Da merkt man: Profi am Werk.",
"Richtige Antwort - dafür gibt es viele Punkte!",
"Weiter so!",
"Wow - das war richtig gut!"
];
const falscheAntworten = [
"Oops! War wohl nicht dein Tag.",
"Knapp daneben ist auch vorbei!",
"Das war... kreativ!",
"Wenn man schon alles wüsste, wäre das Quiz doch sinnlos.",
"Deine Antwort war einzigartig - leider falsch!",
"Na gut, wenigstens hast du es probiert.",
"Das war eine interessante Theorie...",
"Ein Genie irrt sich auch mal.",
"Eine gute Antwort - aber nicht auf diese Frage.",
"Das ist leider falsch. Mehr gibt es nicht zu sagen.",
"Gute Idee! Aber leider nicht richtig.",
"Oh weh! Ein Fehler. Naja, einer ist keiner.",
"Die Antwort klang richtig, sie ist es aber nicht.",
"Interessanter Ansatz. Nicht hilfreich, aber interessant.",
"Nenn das lieber „alternative Fakten“.",
"Du hast die richtige Antwort doch gewusst: 'Die Seite hat nur nicht geladen.'",
"Jeder fängt mal klein an!",
"Kopf hoch - weiter geht's!",
"Nobody is perfect!",
"Du schaffst das - vielleicht ab jetzt.",
"Falsche Antwort, aber guter Wille!",
"Übung macht den Quiz-Meister.",
"Beim nächsten Mal triffst du!",
"Das war nur zum Aufwärmen, oder?",
"Kein Problem - weiter geht's.",
"Gute Frage - aber falsche Antwort."
];
const antwortRichtig = document.getElementById("antwort-richtig");
const antwortFalsch = document.getElementById("antwort-falsch");
if (antwortRichtig) {
antwortRichtig.textContent = richtigeAntworten[Math.floor(Math.random() * richtigeAntworten.length)];
} else if (antwortFalsch) {
antwortFalsch.textContent = falscheAntworten[Math.floor(Math.random() * falscheAntworten.length)];
}
const joinCode = '{{ quiz_game.join_code }}';
const hostId = '{{ host_id }}';
const wsScheme = window.location.protocol === 'https:' ? 'wss' : 'ws';
const gameSocket = new WebSocket(
`${wsScheme}://${window.location.host}/ws/game/${joinCode}/`
);
gameSocket.onmessage = function(e) {
const data = JSON.parse(e.data);
if (data.type === 'game_state_update' &&
data.redirect_url &&
(data.action === 'next_question' || data.action === 'finish_game')) {
window.location.href = data.redirect_url;
} else if (data.type === 'answer_stats') {
updateAnswerStats(data.stats);
}
};
gameSocket.onclose = function(e) {
// Normal closure (code 1000) oder Navigation zu einer anderen Seite (code 1001)
if (e.code === 1000 || e.code === 1001) {
console.log('Game socket closed normally');
} else {
console.error('Game socket closed unexpectedly', e.code);
}
};
{% if is_host %}
// Add click handlers for host buttons
{% if is_last_question %}
var finishButton = document.getElementById('finish-button');
if (finishButton) {
finishButton.addEventListener('click', function() {
gameSocket.send(JSON.stringify({
'type': 'finish_game',
'host_id': hostId
}));
});
}
{% else %}
var nextButton = document.getElementById('next-button');
if (nextButton) {
nextButton.addEventListener('click', function() {
gameSocket.send(JSON.stringify({
'type': 'next_question',
'host_id': hostId
}));
});
}
{% endif %}
{% endif %}
function updateAnswerStats(stats) {
const total = Object.values(stats).reduce((a, b) => a + b, 0) || 1;
for (var optionIndex in stats) {
if (stats.hasOwnProperty(optionIndex)) {
var countElement = document.getElementById('option-count-' + optionIndex);
var fillElement = document.getElementById('option-fill-' + optionIndex);
if (countElement) {
countElement.textContent = stats[optionIndex] + ' Antwort(en)';
}
if (fillElement) {
const percentage = Math.round((stats[optionIndex] / total) * 100);
fillElement.style.width = percentage + '%';
}
}
}
}
// Request answer stats when page loads
gameSocket.onopen = function(e) {
gameSocket.send(JSON.stringify({
'type': 'get_answer_stats'
}));
};
</script>
{% endblock %}