Files
snatchgame/client/src/views/games/GameEndModal.vue

247 lines
8.7 KiB
Vue
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.

<template>
<div v-if="visible" class="end-modal-overlay" @click.self="onDismiss">
<div class="end-modal">
<button class="close-btn" @click="onDismiss" aria-label="Cerrar">×</button>
<div class="title">
<template v-if="isFinal">🏁 Juego finalizado</template>
<template v-else>Resultados Ronda {{ round }} de {{ totalRounds }}</template>
</div>
<div class="scores">
<div
v-for="s in finalScores"
:key="s.sessionId"
class="score-row"
:style="({ '--primary': s.color || '#667eea' }) as any"
>
<div class="left">
<span class="color-dot" :style="{ background: s.color || '#667eea' }"></span>
<span class="name">{{ s.name }}</span>
<span v-if="s.role" class="role" :class="s.role">{{ s.role }}</span>
</div>
<div class="right">
<span class="tokens">🦃 {{ s.pavo }} · 🌽 {{ s.elote }}</span>
<span class="points">{{ s.points }}</span>
</div>
</div>
</div>
<template v-if="isFinal">
<div v-if="adminUnlocked" class="modal-actions">
<button @click="onPrev" class="btn btn-prev-variant">
{{ previousVariantLabel }}
</button>
<button @click="onRestart" class="btn btn-restart-variant">
🔄 {{ currentVariant }}
</button>
<button @click="onNext" class="btn btn-next-variant">
{{ nextVariantLabel }}
</button>
</div>
<div v-else class="round-info clickable" @click="onRoundInfoClick" :title="roundInfoTitle">
(espere a que el administrador continue la partida)
</div>
</template>
<div v-else class="round-info">
Ronda {{ round }} de {{ totalRounds }} aún quedan rondas por jugar. La siguiente comenzará en breve.
</div>
<div class="hint">Se cerrará en {{ remainingSeconds }}s</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onBeforeUnmount, ref, watch } from 'vue';
interface Score {
sessionId: string;
name: string;
role?: 'P1' | 'P2' | '';
pavo: number;
elote: number;
points: number;
color?: string;
}
const props = defineProps<{
visible: boolean;
finalScores: Score[];
variants: string[];
currentVariant: string;
round?: number;
totalRounds?: number;
}>();
const emit = defineEmits<{
(e: 'dismiss'): void;
(e: 'next-variant'): void;
(e: 'previous-variant'): void;
(e: 'restart-variant'): void;
}>();
const remainingSeconds = ref(20);
let intervalId: any = null;
let timeoutId: any = null;
function startTimer() {
stopTimer();
remainingSeconds.value = 20;
intervalId = setInterval(() => {
remainingSeconds.value = Math.max(0, remainingSeconds.value - 1);
}, 1000);
timeoutId = setTimeout(() => {
onDismiss();
}, 20000);
}
function stopTimer() {
if (intervalId) { clearInterval(intervalId); intervalId = null; }
if (timeoutId) { clearTimeout(timeoutId); timeoutId = null; }
}
watch(() => props.visible, (v) => {
if (v) startTimer();
else stopTimer();
}, { immediate: true });
onBeforeUnmount(() => { stopTimer(); });
const totalRounds = computed(() => Math.max(1, Number(props.totalRounds || 3)));
const round = computed(() => Math.max(1, Number(props.round || 1)));
const isFinal = computed(() => round.value >= totalRounds.value);
// Hidden admin unlock via 5 rapid clicks on the round info text
const adminUnlocked = ref(false);
const clickCount = ref(0);
let clickResetTimer: any = null;
function onRoundInfoClick() {
if (clickResetTimer) { clearTimeout(clickResetTimer); clickResetTimer = null; }
clickCount.value += 1;
if (clickCount.value >= 5) {
adminUnlocked.value = true;
} else {
// Small window to keep clicks "seguido"
clickResetTimer = setTimeout(() => { clickCount.value = 0; }, 1200);
}
}
const roundInfoTitle = computed(() => adminUnlocked.value ? 'Controles de variante desbloqueados' : `Clicks: ${clickCount.value}/5 para desbloquear`);
watch(() => props.visible, (v) => {
if (v) {
adminUnlocked.value = false;
clickCount.value = 0;
if (clickResetTimer) { clearTimeout(clickResetTimer); clickResetTimer = null; }
}
});
const nextVariantLabel = computed(() => {
const list = props.variants || [];
const i = Math.max(0, list.indexOf(props.currentVariant));
const next = (i + 1) % Math.max(1, list.length);
return list[next] || '';
});
const previousVariantLabel = computed(() => {
const list = props.variants || [];
const i = Math.max(0, list.indexOf(props.currentVariant));
const prev = (i - 1 + Math.max(1, list.length)) % Math.max(1, list.length);
return list[prev] || '';
});
function onDismiss() { emit('dismiss'); }
function onNext() { emit('next-variant'); }
function onPrev() { emit('previous-variant'); }
function onRestart() { emit('restart-variant'); }
</script>
<style scoped>
.end-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.40); display:flex; align-items:center; justify-content:center; z-index: 1200; }
.end-modal {
position: relative;
color:#111;
border-radius: 16px;
padding: 24px 24px 18px;
width: min(520px, 92vw);
/* Glassmorphism mejorado: más blanco pero transparente */
background: rgba(255, 255, 255, 0.784);
border: 1px solid rgba(255,255,255,0.8);
box-shadow: 0 16px 32px rgba(0,0,0,0.15), inset 0 1px 0 rgba(255,255,255,0.6);
backdrop-filter: blur(20px) saturate(120%);
-webkit-backdrop-filter: blur(20px) saturate(120%);
}
.end-modal .close-btn { position:absolute; top:8px; right:8px; width:32px; height:32px; border-radius: 8px; border:1px solid #e5e7eb; background:#f8fafc; color:#111; font-weight:800; cursor:pointer; }
.end-modal .close-btn:hover { background:#eef2ff; }
.end-modal .title { font-size: 20px; font-weight: 900; margin-bottom: 8px; }
.end-modal .scores { display:flex; flex-direction:column; gap:8px; margin: 8px 0 12px; }
.end-modal .score-row {
display:flex; align-items:center; justify-content:space-between; gap:8px;
background: linear-gradient(135deg, color-mix(in srgb, var(--primary) 6%, white) 0%, #ffffff 100%);
border:1px solid color-mix(in srgb, var(--primary) 20%, #e6e9ff);
border-left: 4px solid var(--primary);
border-radius: 10px; padding:8px 10px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08), 0 1px 3px rgba(0, 0, 0, 0.06);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.end-modal .score-row:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.08);
}
.end-modal .score-row .left { display:flex; align-items:center; gap:8px; }
.end-modal .score-row .right { display:flex; align-items:center; gap:20px; }
.end-modal .score-row .tokens { margin-right: 12px; }
.end-modal .score-row .color-dot { width:10px; height:10px; border-radius:50%; box-shadow: 0 0 0 2px #fff inset; }
.end-modal .score-row .name { font-weight:800; color:#1f2937; }
.end-modal .score-row .tokens { font-weight:700; color:#374151; }
.end-modal .score-row .points { font-weight:900; color: var(--primary); }
.end-modal .score-row .role { font-size:12px; padding:2px 8px; border-radius:10px; background:#f0f0f0; color:#555; }
.end-modal .score-row .role.P1,
.end-modal .score-row .role.P2 { background: color-mix(in srgb, var(--primary) 15%, white); color: var(--primary); }
.end-modal .modal-actions {
margin: 16px 0;
display: flex;
gap: 12px;
justify-content: center;
align-items: center;
}
.end-modal .btn-next-variant, .end-modal .btn-prev-variant, .end-modal .btn-restart-variant {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 12px 16px;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
min-width: 85px;
}
.end-modal .btn-prev-variant {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
box-shadow: 0 4px 12px rgba(240, 147, 251, 0.3);
}
.end-modal .btn-restart-variant {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
box-shadow: 0 4px 12px rgba(79, 172, 254, 0.3);
}
.end-modal .btn-next-variant:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(102, 126, 234, 0.4);
}
.end-modal .btn-prev-variant:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(240, 147, 251, 0.4);
}
.end-modal .btn-restart-variant:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(79, 172, 254, 0.4);
}
.end-modal .hint { font-size: 12px; color:#6b7280; text-align:right; }
.round-info { margin: 10px 2px 2px; font-size: 13px; font-weight:600; color:#334155; text-align:center; }
.round-info.clickable { cursor: pointer; user-select: none; }
.round-info.clickable:hover { filter: brightness(0.95); }
</style>