ux mejoras

This commit is contained in:
2025-08-28 18:31:11 -06:00
parent fa46d3d106
commit 6f2f7a4e12
2 changed files with 63 additions and 65 deletions

View File

@@ -41,6 +41,12 @@
placeholder="Ingresa tu nombre"
class="name-input"
maxlength="20"
autocomplete="off"
autocapitalize="off"
autocorrect="off"
spellcheck="false"
name="sg_player_name"
inputmode="text"
/>
<button @click="updateName" class="btn btn-secondary">Confirmar Nombre</button>
</div>
@@ -182,7 +188,7 @@ onMounted(async () => {
const room = await colyseusService.joinLobby();
colorInput.value = colyseusService.playerColor.value || '#667eea';
inputName.value = colyseusService.playerName.value || '';
// Mantener el input vacío por defecto; no precargar nombres previos
let resumed = false;
const guardResume = () => { if (resumed) return true; resumed = true; return false; };
@@ -461,7 +467,8 @@ function dismissBanner() {
function startEditName() {
editingName.value = true;
inputName.value = colyseusService.playerName.value || '';
// Siempre iniciar la edición con el campo vacío (sin sugerencias)
inputName.value = '';
nextTick(() => {
nameInputRef.value?.focus();
});

View File

@@ -27,26 +27,19 @@
</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">
(espera a que el administrador continúe la partida)
<div class="modal-actions">
<button @click="onControlAttempt('prev')" class="btn btn-prev-variant" :class="{ locked: !adminUnlocked }" :aria-disabled="!adminUnlocked">Anterior</button>
<button @click="onControlAttempt('restart')" class="btn btn-restart-variant" :class="{ locked: !adminUnlocked }" :aria-disabled="!adminUnlocked">Repetir</button>
<button @click="onControlAttempt('next')" class="btn btn-next-variant" :class="{ locked: !adminUnlocked }" :aria-disabled="!adminUnlocked">Siguiente</button>
</div>
<div v-if="!adminUnlocked && showHint" class="round-info">Controles bloqueados clics: {{ clickCount }}/5 para habilitarlos</div>
<div v-else-if="adminUnlocked" class="round-info">Controles habilitados</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 v-if="!isFinal" class="hint">Se cerrará en {{ remainingSeconds }}s</div>
</div>
</div>
</template>
@@ -100,10 +93,7 @@ function stopTimer() {
if (timeoutId) { clearTimeout(timeoutId); timeoutId = null; }
}
watch(() => props.visible, (v) => {
if (v) startTimer();
else stopTimer();
}, { immediate: true });
// (moved below isFinal definition)
onBeforeUnmount(() => { stopTimer(); });
@@ -111,28 +101,46 @@ 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
// Auto-cierre solo cuando no es final
watch([() => props.visible, () => isFinal.value], ([vis, final]) => {
if (vis && !final) startTimer();
else stopTimer();
}, { immediate: true });
// Hidden admin unlock via 5 rapid clicks on the control buttons
const adminUnlocked = ref(false);
const clickCount = ref(0);
const showHint = ref(false);
let clickResetTimer: any = null;
function onRoundInfoClick() {
function onControlAttempt(kind: 'prev'|'next'|'restart') {
if (adminUnlocked.value) {
if (kind === 'prev') onPrev();
else if (kind === 'next') onNext();
else onRestart();
return;
}
// Locked: count clicks and show hint
showHint.value = true;
if (clickResetTimer) { clearTimeout(clickResetTimer); clickResetTimer = null; }
clickCount.value += 1;
if (clickCount.value >= 5) {
adminUnlocked.value = true;
// Perform the action that was attempted on the unlocking click
if (kind === 'prev') onPrev();
else if (kind === 'next') onNext();
else onRestart();
} else {
// Small window to keep clicks "seguido"
clickResetTimer = setTimeout(() => { clickCount.value = 0; }, 1200);
// Small window to keep clicks consecutivos
clickResetTimer = setTimeout(() => { clickCount.value = 0; showHint.value = false; }, 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;
showHint.value = false;
if (clickResetTimer) { clearTimeout(clickResetTimer); clickResetTimer = null; }
}
});
@@ -199,46 +207,29 @@ function onRestart() { emit('restart-variant'); }
.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;
.end-modal .modal-actions { margin: 12px 0 8px; display: flex; gap: 6px; justify-content: center; align-items: center; flex-wrap: wrap; }
/* Glassy compact buttons, aligned with home style */
.end-modal .btn {
appearance: none;
background: linear-gradient(135deg, rgba(102,126,234,0.24) 0%, rgba(118,75,162,0.24) 100%);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255,255,255,0.55);
color: #243147;
padding: 6px 10px;
border-radius: 10px;
font-size: 12px;
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);
transition: transform 0.15s ease, box-shadow 0.2s ease, background 0.2s ease;
box-shadow: 0 6px 18px rgba(102,126,234,0.12);
min-width: 72px;
-webkit-tap-highlight-color: transparent;
}
.end-modal .btn:hover { transform: translateY(-1px); box-shadow: 0 6px 16px rgba(118,75,162,0.16); }
.end-modal .btn:active { transform: translateY(0); box-shadow: 0 2px 8px rgba(0,0,0,0.12); }
.end-modal .btn:focus, .end-modal .btn:focus-visible { outline: none; }
.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; }