simplificando UI del lobby
This commit is contained in:
@@ -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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user