simplificando UI del lobby
All checks were successful
build-and-deploy / build (push) Successful in 20s
build-and-deploy / deploy (push) Successful in 10s

This commit is contained in:
2025-08-28 22:37:26 -06:00
parent a656165f28
commit 5d1bb00173

View File

@@ -26,10 +26,11 @@
</div> </div>
</div> </div>
</div> </div>
<h1 class="title"> <div class="hero">
<GameLogo size="large" /> Snatch Game <div class="logo-row"><GameLogo size="large" /></div>
</h1> <h1 class="title">Snatch Game</h1>
<div class="subtitle">Arena de intercambio social</div> <div class="subtitle">Arena de intercambio social</div>
</div>
<!-- Error notification for reconnection issues --> <!-- Error notification for reconnection issues -->
<div v-if="reconnectionError" class="error-notification"> <div v-if="reconnectionError" class="error-notification">
@@ -42,56 +43,53 @@
</div> </div>
<div class="player-section"> <div class="player-section">
<div class="name-input-group" v-if="!nameConfirmed || editingName"> <!-- Estado inicial: solo nombre + color + stats (compacto en móvil) -->
<input <div v-if="!nameConfirmed || editingName" class="setup-wrapper">
ref="nameInputRef" <div class="name-input-group compact">
v-model="inputName" <input
@keyup.enter="updateName" ref="nameInputRef"
type="text" v-model="inputName"
placeholder="Ingresa tu nombre" @keyup.enter="updateName"
class="name-input" type="text"
maxlength="20" placeholder="Ingresa tu nombre"
autocomplete="off" class="name-input"
autocapitalize="off" maxlength="20"
autocorrect="off" autocomplete="off"
spellcheck="false" autocapitalize="off"
name="sg_player_name" autocorrect="off"
inputmode="text" spellcheck="false"
/> name="sg_player_name"
<button @click="updateName" class="btn btn-secondary">Confirmar Nombre</button> inputmode="text"
/>
<button @click="updateName" class="btn btn-secondary btn-compact">Confirmar</button>
</div>
<div class="color-picker">
<label class="color-label">Color:</label>
<input type="color" v-model="colorInput" @change="updateColor" class="color-input" />
</div>
<div class="preview">
<PlayerStats :player="previewPlayer" :highlight="true" />
</div>
</div> </div>
<div class="main-actions inline">
<button @click="handleQuickPlay" class="btn btn-primary btn-large" :disabled="isJoining || !nameConfirmed"> <!-- Nombre confirmado: ocultar inputs, mostrar 'Jugando como' (clickable para editar) y CTA Jugar con glasmorphism -->
<div v-else class="play-cta">
<div
class="current-name ready clickable"
@click="startEditName"
title="Editar nombre y color"
>
Jugando como: <span class="player-name">{{ playerName || 'invitado' }}</span><span class="edit-hint"> ()</span>
</div>
<button
@click="handleQuickPlay"
class="btn btn-play glass"
:style="({ '--accent': accentColor } as any)"
:disabled="isJoining"
>
<span v-if="!isJoining">Jugar</span> <span v-if="!isJoining">Jugar</span>
<span v-else>Buscando partida...</span> <span v-else>Buscando partida...</span>
</button> </button>
<div v-if="!nameConfirmed" class="hint">Antes de jugar, presiona "Confirmar Nombre" para confirmar tu nombre.</div>
</div>
<div class="current-name" :class="{ clickable: nameConfirmed }" @click="nameConfirmed ? startEditName() : null" :title="nameConfirmed ? 'Editar nombre' : ''">
Jugando como: <span class="player-name">{{ playerName || 'invitado' }}</span><span v-if="nameConfirmed" class="edit-hint"> ()</span>
</div>
<div class="color-picker">
<label class="color-label">Color:</label>
<input type="color" v-model="colorInput" @change="updateColor" class="color-input" />
</div>
<div class="preview">
<PlayerStats :player="previewPlayer" :highlight="true" />
</div>
</div>
<div class="qr-section compact">
<div class="qr-container compact">
<div class="qr-inline">
<canvas ref="qrCanvas" class="qr-canvas small"></canvas>
<div class="qr-side">
<p class="url-display small" title="{{ gameUrl }}">{{ shortUrl }}</p>
<div class="qr-actions tight">
<button @click="copyUrl" class="btn btn-copy tiny">Copiar</button>
<button @click="shareQR" class="btn btn-share tiny">Compartir</button>
</div>
<p class="uuid-display small">UUID: {{ routeUuid.substring(0, 8) }}...</p>
</div>
</div>
</div> </div>
</div> </div>
@@ -108,7 +106,7 @@ import AppCredits from '../components/AppCredits.vue';
import { useRouter, useRoute } from 'vue-router'; import { useRouter, useRoute } from 'vue-router';
import { colyseusService } from '../services/colyseus'; import { colyseusService } from '../services/colyseus';
import { getStateCallbacks } from 'colyseus.js'; import { getStateCallbacks } from 'colyseus.js';
import QRCode from 'qrcode'; // QR eliminado
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
@@ -116,7 +114,6 @@ const routeUuid = computed(() => (route.params as any)?.uuid as string || '');
const inputName = ref(''); const inputName = ref('');
const isJoining = ref(false); const isJoining = ref(false);
const colorInput = ref('#667eea'); const colorInput = ref('#667eea');
const qrCanvas = ref<HTMLCanvasElement>();
// Reconnection error state // Reconnection error state
const reconnectionError = ref(false); const reconnectionError = ref(false);
@@ -129,21 +126,7 @@ let installTimer: any = null;
const editingName = ref(false); const editingName = ref(false);
const nameInputRef = ref<HTMLInputElement | null>(null); const nameInputRef = ref<HTMLInputElement | null>(null);
// QR Code computed properties // QR eliminado
const gameUrl = computed(() => {
const baseUrl = window.location.origin;
return `${baseUrl}/${routeUuid.value}`;
});
const shortUrl = computed(() => {
try {
const u = new URL(gameUrl.value);
// usar host corto + path uuid
return `${u.host}/${routeUuid.value}`;
} catch {
return gameUrl.value;
}
});
const isStandalone = () => const isStandalone = () =>
(window.matchMedia && window.matchMedia('(display-mode: standalone)').matches) || (window.matchMedia && window.matchMedia('(display-mode: standalone)').matches) ||
@@ -185,6 +168,8 @@ const previewPlayer = computed(() => ({
color: colorInput.value || playerColor.value color: colorInput.value || playerColor.value
})); }));
const accentColor = computed(() => playerColor.value || colorInput.value || '#667eea');
// Define missing reactive variables // Define missing reactive variables
const availableRooms = ref<any[]>([]); const availableRooms = ref<any[]>([]);
const totalPlayers = ref(0); const totalPlayers = ref(0);
@@ -349,9 +334,6 @@ onMounted(async () => {
} }
}); });
// Generate QR code after lobby is set up
await generateQRCode();
// Show install banner if applicable (e.g., iOS or after BIP fired) // Show install banner if applicable (e.g., iOS or after BIP fired)
maybeShowInstallBanner(); maybeShowInstallBanner();
} catch (error) { } catch (error) {
@@ -404,51 +386,7 @@ async function handleQuickPlay() {
// QR Code functions // QR eliminado
async function generateQRCode() {
await nextTick();
if (qrCanvas.value && routeUuid.value) {
try {
// Responsive QR size based on screen width
const isMobile = window.innerWidth <= 767;
const qrSize = isMobile ? 100 : 140; // aún más compacto
await QRCode.toCanvas(qrCanvas.value, gameUrl.value, {
width: qrSize,
margin: 0,
color: {
dark: '#000000',
light: '#FFFFFF'
}
});
} catch (error) {
console.error('Error generating QR code:', error);
}
}
}
async function copyUrl() {
try {
await navigator.clipboard.writeText(gameUrl.value);
// Could add a toast notification here
console.log('URL copied to clipboard');
} catch (error) {
console.error('Failed to copy URL:', error);
}
}
function shareQR() {
if (navigator.share) {
navigator.share({
title: 'Únete a mi Snatch Game',
text: `Únete a ${playerName.value || 'mí'} en Snatch Game!`,
url: gameUrl.value
}).catch(err => console.log('Error sharing:', err));
} else {
// Fallback: copy to clipboard
copyUrl();
}
}
function goToSelector() { function goToSelector() {
router.push('/'); router.push('/');
@@ -479,7 +417,6 @@ function dismissBanner() {
function startEditName() { function startEditName() {
editingName.value = true; editingName.value = true;
// Siempre iniciar la edición con el campo vacío (sin sugerencias)
inputName.value = ''; inputName.value = '';
nextTick(() => { nextTick(() => {
nameInputRef.value?.focus(); nameInputRef.value?.focus();
@@ -493,14 +430,9 @@ function startEditName() {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px; padding: 20px;
overflow-y: auto; overflow-y: auto;
} display: flex;
align-items: center;
@media (min-width: 768px) { justify-content: center;
.lobby {
display: flex;
align-items: center;
justify-content: center;
}
} }
.lobby-container { .lobby-container {
@@ -576,14 +508,13 @@ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
} }
} }
.hero { display: flex; flex-direction: column; align-items: center; }
.logo-row { display: flex; justify-content: center; margin-top: 2px; margin-bottom: 2px; }
.title { .title {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
font-size: 3rem; font-size: 3rem;
text-align: center; text-align: center;
margin: 0; margin: 2px 0 0 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text; -webkit-background-clip: text;
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
@@ -599,7 +530,7 @@ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
.subtitle { .subtitle {
text-align: center; text-align: center;
color: #666; color: #666;
margin-top: 10px; margin-top: 2px;
font-size: 1.2rem; font-size: 1.2rem;
} }
@@ -656,7 +587,6 @@ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
.player-section { .player-section {
margin: 30px 0; margin: 30px 0;
padding: 20px; padding: 20px;
background: #f8f9fa;
border-radius: 10px; border-radius: 10px;
} }
@@ -848,6 +778,53 @@ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
margin-bottom: 15px; margin-bottom: 15px;
} }
/* Compact setup area */
.setup-wrapper { margin-top: 8px; }
.name-input-group.compact { display:flex; gap: 10px; align-items: center; }
.name-input-group.compact .name-input { flex: 1 1 auto; min-width: 0; }
.btn-compact { padding: 10px 14px; border-radius: 8px; }
/* Play CTA with glasmorphism and color glow */
.play-cta { display: flex; flex-direction: column; align-items: center; gap: 10px; margin: 28px 0; }
.current-name.ready { color: #475569; font-size: 1rem; }
.btn-play {
position: relative;
padding: 16px 32px;
font-size: 22px;
border-radius: 16px;
color: #0f172a;
background: rgba(255,255,255,0.55);
border: 1px solid rgba(255,255,255,0.4);
box-shadow: none;
overflow: hidden;
min-width: 300px;
}
.btn-play.glass { backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); }
.btn-play::before {
content: "";
position: absolute;
left: -22%;
right: -22%;
bottom: -50%;
height: 160%;
background:
radial-gradient(70% 70% at 50% 100%, var(--accent, #667eea) 0%, var(--accent, #667eea) 42%, rgba(255,255,255,0) 85%);
filter: blur(28px);
opacity: 0.75;
pointer-events: none;
}
.btn-play::after {
content: "";
position: absolute;
left: 0; right: 0; bottom: 0; top: 0;
background: linear-gradient(to top, var(--accent, #667eea) 0%, rgba(255,255,255,0) 55%);
filter: blur(18px);
opacity: 0.45;
pointer-events: none;
}
.btn-play:hover { transform: translateY(-2px); box-shadow: none; }
.btn-play:disabled { opacity: 0.8; cursor: not-allowed; }
.player-tag { .player-tag {
padding: 6px 12px; padding: 6px 12px;
background: white; background: white;
@@ -1006,6 +983,22 @@ margin: 0 0 20px 0;
margin: 0; margin: 0;
} }
/* Back button ultra-compact on mobile */
.topbar { gap: 6px; margin-bottom: 6px; }
.lobby-header { margin-bottom: 6px; }
.btn-back { padding: 4px 8px; font-size: 12px; border-radius: 6px; }
.btn-back:hover { transform: none; box-shadow: none; }
/* Install banner: much more compact on mobile */
.install-banner { padding: 6px 8px; gap: 8px; border-radius: 8px; }
.install-banner-content { gap: 6px; }
.install-icon { width: 18px; height: 18px; }
.install-text strong { font-size: 12px; }
.install-text span { font-size: 11px; }
.install-actions { gap: 4px; }
.btn-install { padding: 4px 8px; font-size: 11px; border-radius: 6px; }
.btn-dismiss { padding: 2px 4px; font-size: 12px; }
.qr-container { padding: 14px; } .qr-container { padding: 14px; }
.qr-code-wrapper { padding: 10px; margin: 10px 0; } .qr-code-wrapper { padding: 10px; margin: 10px 0; }
.qr-canvas.small { width: 100px !important; height: 100px !important; } .qr-canvas.small { width: 100px !important; height: 100px !important; }
@@ -1039,6 +1032,15 @@ margin: 0 0 20px 0;
font-size: 18px; font-size: 18px;
width: 100%; width: 100%;
} }
/* Compact the setup area further on mobile */
.name-input-group.compact { gap: 8px; }
.name-input-group.compact .name-input { padding: 10px; font-size: 15px; }
.btn-compact { padding: 10px 12px; font-size: 14px; }
.color-picker { gap: 8px; margin-top: 8px; }
.color-input { width: 38px; height: 28px; box-shadow: 0 4px 10px rgba(0,0,0,0.2); }
.preview { margin-top: 6px; }
.play-cta .btn-play { width: 100%; max-width: 100%; min-width: 0; padding: 14px 20px; font-size: 20px; border-radius: 14px; }
} }
</style> </style>