Files
qivip/django/templates/play/game/winner_podest.html

594 lines
21 KiB
HTML
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. 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 %}
{% if is_last_question and is_host %}
<!-- Siegerpodest -->
<div class="min-h-screen">
<h2 class="w-full flex justify-center dark:text-white font-bold text-2xl ">&#127942; Siegerpodest</h2>
<h4 class="w-full flex justify-center dark:text-white font-bold">Wer ist hier der Quiz-Master? </h4>
{% with participant=participants.0 %}
<div class="w-full flex justify-center">
<h1 id="winner-text" class="bg-gradient-to-r from-yellow-400 via-yellow-300 to-yellow-600 bg-clip-text text-transparent font-bold hidden animate-ping px-2 max-w-screen-sm text-center break-words">
Glückwunsch, {{ participant.display_name }}!
</h1>
</div>
{% endwith %}
<div id="podest-container" class="w-full absolute bottom-0 flex justify-center items-end gap-2 md:gap-4 mb-2 dark:text-white">
<!-- Platz 2 links -->
{% with participant=participants.1 %}
<div class="flex flex-col shrink items-center break-words max-w-[6rem] md:max-w-[8rem] lg:max-w-[10rem] overflow-x-auto pt-10">
<span class="display_name p-2 text-gray-600 font-black text-xl md:text-2xl lg:text-3xl opacity-0 transition-opacity duration-1000 dark:text-white hyphens-auto" id="display_name-1">{{ participant.display_name }}</span>
<p data-score="{{ participant.score }}" data-last-score="{{ participant.last_score }}" class="opacity-0 transition-opacity duration-1000 dark:text-white font-bold show-score text-sm md:text-lg lg:text-xl text-gray-400" id="score-1">{{ participant.score }}</p>
<div data-final-height="33" id="podium-1" class="border-2 border-gray-200 transition-all duration-1000 ease-out overflow-hidden podium h-24 md:h-[12rem] lg:h-[16rem] w-24 md:w-32 lg:w-40 rounded-t-md font-bold text-center text-base shadow-lg bg-gradient-to-b from-gray-400 via-gray-300 to-gray-300 flex flex-col items-center justify-center text-black dark:text-white cursor-pointer leading-tight">
<span class="size-10 md:size-18 rounded-full bg-white text-black text-center flex items-center justify-center font-bold text-3xl md:text-5xl lg:text-6xl">🥈</span>
</div>
</div>
{% endwith %}
<!-- Platz 1 mitte -->
{% with participant=participants.0 %}
<div class="flex flex-col shrink items-center break-words max-w-[6rem] md:max-w-[8rem] lg:max-w-[10rem] overflow-x-auto pt-10">
<div id="resultFirst"></div>
<span class="display_name p-2 text-gray-600 font-black text-xl md:text-3xl lg:text-4xl opacity-0 transition-opacity duration-1000 bg-gradient-to-r from-yellow-400 via-yellow-300 to-yellow-600 bg-clip-text text-transparent hyphens-auto" id="display_name-0">{{ participant.display_name }}</span>
<p data-score="{{ participant.score }}" data-last-score="{{ participant.last_score }}" class="opacity-0 transition-opacity duration-1000 dark:text-white text-yellow-500 font-bold show-score text-sm md:text-lg lg:text-xl" id="score-0">{{ participant.score }}</p>
<div data-final-height="45" id="podium-0" class="border-2 border-yellow-500 transition-all duration-1000 ease-out overflow-hidden podium h-32 md:h-[16rem] lg:h-[22rem] w-24 md:w-32 lg:w-40 rounded-t-md font-bold text-center text-base shadow-lg bg-gradient-to-b from-yellow-400 via-yellow-300 to-yellow-600 flex flex-col items-center justify-center text-black dark:text-white cursor-pointer leading-tight">
<span id="firstMedal" class="size-12 md:size-22 rounded-full bg-white text-black text-center flex items-center justify-center font-bold text-4xl md:text-6xl lg:text-7xl">🥇</span><br>
</div>
</div>
{% endwith %}
<!-- Platz 3 rechts -->
{% with participant=participants.2 %}
<div class="flex flex-col shrink items-center break-words max-w-[6rem] md:max-w-[8rem] lg:max-w-[10rem] overflow-x-auto pt-10">
<span class="display_name p-2 text-gray-600 font-black text-xl md:text-2xl lg:text-3xl dark:text-white opacity-0 transition-opacity duration-1000 hyphens-auto" id="display_name-2">{{ participant.display_name }}</span>
<p data-score="{{ participant.score }}" data-last-score="{{ participant.last_score }}" class="opacity-0 transition-opacity duration-1000 dark:text-white text-orange-500 font-bold show-score text-sm md:text-lg lg:text-xl" id="score-2">{{ participant.score }}</p>
<div data-final-height="25" id="podium-2" class="border-2 border-orange-500 transition-all duration-1000 ease-out overflow-hidden podium h-20 md:h-[10rem] lg:h-[14rem] w-24 md:w-32 lg:w-40 rounded-t-md font-bold text-center text-base shadow-lg bg-gradient-to-b from-orange-500 via-orange-400 to-orange-700 flex flex-col items-center justify-center text-black dark:text-white cursor-pointer leading-tight">
<span class="size-10 md:size-18 rounded-full bg-white text-black text-center flex items-center justify-center font-bold text-3xl md:text-5xl lg:text-6xl">🥉</span>
</div>
</div>
{% endwith %}
</div>
</div>
<div id="thanks" class="hidden -mt-10">
<div class="w-full flex justify-center">
<h1 class="bg-gradient-to-r from-yellow-400 via-yellow-300 to-yellow-600 bg-clip-text text-transparent font-bold px-2 max-w-screen-sm text-center break-words">
Vielen Dank fürs Spielen!
</h1>
</div>
<div class="flex justify-center">
<button id="finish-button" class=" w-3/4 md:w-1/2 bottom-0 left-0 p-3 rounded-full bg-blue-600 text-white font-bold hover:bg-blue-700 transition-colors duration-200">
Quiz beenden
</button>
</div>
</div>
{% else %}
<div class="container mx-auto px-4">
{% with participant=participants.0 %}
<div class="w-full flex justify-center">
<h1 id="winner-text" class="bg-gradient-to-r from-yellow-400 via-yellow-300 to-yellow-600 bg-clip-text text-transparent font-bold hidden animate-ping px-2 max-w-screen-sm text-center break-words">
Glückwunsch, {{ participant.display_name }}!
</h1>
</div>
{% endwith %}
<div class="bg-white rounded-lg shadow-md p-6 mb-6 dark:bg-transparent dark:text-white">
<h1 id="result-text" class="text-3xl w-full flex justify-center dark:text-white font-bold">&#127942; Wer ist hier der Quiz-Master?</h1>
<h4 id="info" class="mt-4 w-full flex justify-center dark:text-white font-bold">Nach der Siegerehrung siehst du deine Platzierung!</h4>
<div id="participant-score" class="mb-6 ">
<div id="scoreboard" class="leading-relaxed">
{% for participant in participants %}
{% if request.session.mode != "learn" %}
{% if participant == my_participant or is_host %}
<div id="player_score" class="hidden dark:bg-transparent mt-2 shadow-sm p-4 bg-gray-100 rounded-lg border-2 border {% if forloop.counter <= 3 %}
{% if forloop.counter == 1 %}border-yellow-400{% endif %}
{% if forloop.counter == 2 %}border-gray-400{% endif %}
{% if forloop.counter == 3 %}border-orange-400{% endif %}
{% else %}
border-transparent{% endif %}">
<div class="flex flex-col items-start gap-1">
<div class="text-4xl 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-3xl dark:text-white " 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="dark:text-white text-gray-600 font-bold show-score text-xl" id="score-{{ forloop.counter0 }}">{{ participant.score }} Punkte</p>
{% endif %}
</div>
</div>
{% endif %}
{% endfor %}
</div>
</div>
{% endif %}
<style>
.podium {
height: 0;
}
.spotlight-wrapper::before {
content: "";
position: absolute;
top: -800px; /* Start oben außerhalb */
left: 50%;
transform: translateX(-50%);
width: 15rem;
height: 700px;
pointer-events: none;
background: radial-gradient(
ellipse 60% 100% at center bottom,
rgba(254, 254, 178, 0.9) 0%,
rgba(252, 236, 132, 0.7) 25%,
rgba(255, 230, 130, 0.5) 55%,
rgba(255, 210, 100, 0.1) 85%,
rgba(255, 180, 70, 0) 100%
);
filter: blur(24px) brightness(1.2);
opacity: 0; /* Start unsichtbar */
animation: slideInOutSpotlight 6s ease forwards , fadeOutSpotlight 6s 6s ease forwards; /* Dauer anpassen */
z-index: -1;
box-shadow: 0 0 60px 30px rgba(255, 240, 150, 0.15);
}
@keyframes slideInOutSpotlight {
0% {
top: -800px;
opacity: 0;
}
20% {
top: -640px; /* Endposition */
opacity: 1;
}
100% {
top: -640px;
opacity: 1;
}
}
@keyframes fadeOutSpotlight {
0% {
top: -640px;
opacity: 1;
}
100% {
top: -640px;
opacity: 0;
}
}
</style>
{% endblock %}
</div>
{% block extra_js %}
{% if is_last_question %}
<script>
document.addEventListener('DOMContentLoaded', () => {
const animationDuration = 1500; // Dauer der Wachstumsanimation
const waitTime=3000;
const podiums = [
{ id: 'podium-2', delay: 2000 }, // immer vorheriges von Bedeutung
{ id: 'podium-1', delay: 2000 },
{ id: 'podium-0', delay: 0 },
];
let totalDelay = 0;
podiums.forEach(({ delay }) => {
totalDelay += delay + waitTime;
});
const totalAnimationTime = totalDelay + animationDuration;
setTimeout(() => {
show_score();
}, totalAnimationTime);
totalDelay = 0;
podiums.forEach(({ id, delay }) => {
const podium = document.getElementById(id);
if (!podium) return;
const finalHeight = parseInt(podium.dataset.finalHeight) || 160;
podium.style.transition = `height ${animationDuration}ms ease-in-out`;
podium.style.height = '0px';
// Wachstum starten nach gesamter Wartezeit
setTimeout(() => {
podium.style.height = `${finalHeight}vh`;
}, totalDelay);
// Name & Score anzeigen nach Balken-Animation
setTimeout(() => {
if (id === 'podium-0') {
let name = document.getElementById(`display_name-0`);
let score = document.getElementById(`score-0`);
let winnerText= document.getElementById(`winner-text`);
let thanks= document.getElementById(`thanks`);
// 1. Lichtkegel anzeigen
if (resultFirst) {
resultFirst.classList.add('spotlight-wrapper');
score.classList.add('opacity-0');
name.classList.add('opacity-0');
}
// 2. Nach kurzer Wartezeit Name & Score anzeigen
setTimeout(() => {
if (name) {
name.classList.add('opacity-100', 'animate-bounce','spotlight-text'); // z.B. sichtbar + Effekt
}
if (winnerText) {
winnerText.classList.remove('hidden');
}
if (thanks) {
thanks.classList.remove('hidden');
}
if (score) {
score.classList.add('opacity-100'); // z.B. sichtbar + Effekt
}
triggerConfetti();
}, 2000);
document.body.style.overflow = 'auto';
return;
}
const index = id.split('-')[1];
const name = document.getElementById(`display_name-${index}`);
const score = document.getElementById(`score-${index}`);
document.body.style.overflow = 'hidden';
if (name && id !== 'podium-0') setTimeout(() => {
name.classList.add('opacity-100');
name.classList.add('animate-bounce');
if (score) score.classList.add('opacity-100');
}, waitTime);
}, totalDelay + animationDuration);
totalDelay += delay + waitTime; // wichtig: aufsummieren
});
});
// Hilfsfunktion für Pausen
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function triggerConfetti() {
const end = Date.now() + 30 * 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);
})();
}
function show_score() {
let player_score=document.getElementById('player_score');
player_score.classList.remove('hidden');
info=document.getElementById('info');
info.classList.add('hidden');
resultText=document.getElementById('result-text');
resultText.textContent="Dein Ergebnis";
const myName = "{{ my_participant.id|escapejs }}";
const firstPlaceName = "{{ participants.0.id|escapejs }}";
if (myName === firstPlaceName) {
let winnerText= document.getElementById(`winner-text`);
winnerText.classList.remove('hidden');
triggerConfetti();
}
}
</script>
{% endif %}
{% include 'play/game/common_scripts.html' %}
{% if is_host %}
{% include 'play/game/sound.html' %}
{% endif %}
<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 %}
start();
bounce();
{% else %}
start();
{% endif %}
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' || data.action === 'show_winner_podest')) {
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
}));
});
}
var podestButton = document.getElementById('winner-button');
if (podestButton) {
podestButton.addEventListener('click', function() {
gameSocket.send(JSON.stringify({
'type': 'winner_podest',
'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
}));
}, { once: true });
}
{% 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 %}