simplificando UI del lobby
This commit is contained in:
@@ -26,10 +26,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="title">
|
||||
<GameLogo size="large" /> Snatch Game
|
||||
</h1>
|
||||
<div class="subtitle">Arena de intercambio social</div>
|
||||
<div class="hero">
|
||||
<div class="logo-row"><GameLogo size="large" /></div>
|
||||
<h1 class="title">Snatch Game</h1>
|
||||
<div class="subtitle">Arena de intercambio social</div>
|
||||
</div>
|
||||
|
||||
<!-- Error notification for reconnection issues -->
|
||||
<div v-if="reconnectionError" class="error-notification">
|
||||
@@ -42,56 +43,53 @@
|
||||
</div>
|
||||
|
||||
<div class="player-section">
|
||||
<div class="name-input-group" v-if="!nameConfirmed || editingName">
|
||||
<input
|
||||
ref="nameInputRef"
|
||||
v-model="inputName"
|
||||
@keyup.enter="updateName"
|
||||
type="text"
|
||||
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>
|
||||
<!-- Estado inicial: solo nombre + color + stats (compacto en móvil) -->
|
||||
<div v-if="!nameConfirmed || editingName" class="setup-wrapper">
|
||||
<div class="name-input-group compact">
|
||||
<input
|
||||
ref="nameInputRef"
|
||||
v-model="inputName"
|
||||
@keyup.enter="updateName"
|
||||
type="text"
|
||||
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 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 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-else>Buscando partida...</span>
|
||||
</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>
|
||||
|
||||
@@ -108,7 +106,7 @@ import AppCredits from '../components/AppCredits.vue';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { colyseusService } from '../services/colyseus';
|
||||
import { getStateCallbacks } from 'colyseus.js';
|
||||
import QRCode from 'qrcode';
|
||||
// QR eliminado
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
@@ -116,7 +114,6 @@ const routeUuid = computed(() => (route.params as any)?.uuid as string || '');
|
||||
const inputName = ref('');
|
||||
const isJoining = ref(false);
|
||||
const colorInput = ref('#667eea');
|
||||
const qrCanvas = ref<HTMLCanvasElement>();
|
||||
|
||||
// Reconnection error state
|
||||
const reconnectionError = ref(false);
|
||||
@@ -129,21 +126,7 @@ let installTimer: any = null;
|
||||
const editingName = ref(false);
|
||||
const nameInputRef = ref<HTMLInputElement | null>(null);
|
||||
|
||||
// QR Code computed properties
|
||||
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;
|
||||
}
|
||||
});
|
||||
// QR eliminado
|
||||
|
||||
const isStandalone = () =>
|
||||
(window.matchMedia && window.matchMedia('(display-mode: standalone)').matches) ||
|
||||
@@ -185,6 +168,8 @@ const previewPlayer = computed(() => ({
|
||||
color: colorInput.value || playerColor.value
|
||||
}));
|
||||
|
||||
const accentColor = computed(() => playerColor.value || colorInput.value || '#667eea');
|
||||
|
||||
// Define missing reactive variables
|
||||
const availableRooms = ref<any[]>([]);
|
||||
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)
|
||||
maybeShowInstallBanner();
|
||||
} catch (error) {
|
||||
@@ -404,51 +386,7 @@ async function handleQuickPlay() {
|
||||
|
||||
|
||||
|
||||
// QR Code functions
|
||||
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();
|
||||
}
|
||||
}
|
||||
// QR eliminado
|
||||
|
||||
function goToSelector() {
|
||||
router.push('/');
|
||||
@@ -479,7 +417,6 @@ function dismissBanner() {
|
||||
|
||||
function startEditName() {
|
||||
editingName.value = true;
|
||||
// Siempre iniciar la edición con el campo vacío (sin sugerencias)
|
||||
inputName.value = '';
|
||||
nextTick(() => {
|
||||
nameInputRef.value?.focus();
|
||||
@@ -493,14 +430,9 @@ function startEditName() {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.lobby {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.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 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
font-size: 3rem;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
margin: 2px 0 0 0;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
@@ -599,7 +530,7 @@ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
.subtitle {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
margin-top: 10px;
|
||||
margin-top: 2px;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
@@ -656,7 +587,6 @@ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
.player-section {
|
||||
margin: 30px 0;
|
||||
padding: 20px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
@@ -848,6 +778,53 @@ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
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 {
|
||||
padding: 6px 12px;
|
||||
background: white;
|
||||
@@ -1006,6 +983,22 @@ margin: 0 0 20px 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-code-wrapper { padding: 10px; margin: 10px 0; }
|
||||
.qr-canvas.small { width: 100px !important; height: 100px !important; }
|
||||
@@ -1039,6 +1032,15 @@ margin: 0 0 20px 0;
|
||||
font-size: 18px;
|
||||
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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user