Implementado logo
This commit is contained in:
BIN
SnatchGame.png
Normal file
BIN
SnatchGame.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 MiB |
@@ -1,10 +1,27 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="es">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="icon" href="/favicon.ico">
|
<link rel="icon" type="image/png" href="/favicon.png?v=2">
|
||||||
|
<link rel="apple-touch-icon" href="/SnatchGame.png?v=2">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Snatch Game - Competitive Clicker</title>
|
<meta name="description" content="SnatchGame - Arena de intercambio social. Juego competitivo de estrategia y negociación.">
|
||||||
|
<meta name="keywords" content="juego, estrategia, competitivo, intercambio, social, tokens">
|
||||||
|
<meta name="author" content="SnatchGame">
|
||||||
|
|
||||||
|
<!-- Open Graph / Facebook -->
|
||||||
|
<meta property="og:type" content="website">
|
||||||
|
<meta property="og:title" content="SnatchGame - Arena de intercambio social">
|
||||||
|
<meta property="og:description" content="Juego competitivo de estrategia y negociación donde los jugadores intercambian tokens.">
|
||||||
|
<meta property="og:image" content="/SnatchGame.png?v=2">
|
||||||
|
|
||||||
|
<!-- Twitter -->
|
||||||
|
<meta property="twitter:card" content="summary_large_image">
|
||||||
|
<meta property="twitter:title" content="SnatchGame - Arena de intercambio social">
|
||||||
|
<meta property="twitter:description" content="Juego competitivo de estrategia y negociación donde los jugadores intercambian tokens.">
|
||||||
|
<meta property="twitter:image" content="/SnatchGame.png?v=2">
|
||||||
|
|
||||||
|
<title>SnatchGame - Arena de intercambio social</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
BIN
client/public/SnatchGame.png
Normal file
BIN
client/public/SnatchGame.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 MiB |
BIN
client/public/favicon.png
Normal file
BIN
client/public/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 MiB |
61
client/src/components/GameLogo.vue
Normal file
61
client/src/components/GameLogo.vue
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<template>
|
||||||
|
<img
|
||||||
|
:src="logoSrc"
|
||||||
|
alt="SnatchGame"
|
||||||
|
:class="['game-logo', sizeClass]"
|
||||||
|
:style="customSize ? { maxWidth: customSize, maxHeight: customSize } : {}"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
size?: 'small' | 'medium' | 'large' | 'custom';
|
||||||
|
customSize?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
size: 'medium'
|
||||||
|
});
|
||||||
|
|
||||||
|
const logoSrc = '/SnatchGame.png?v=2';
|
||||||
|
|
||||||
|
const sizeClass = computed(() => `size-${props.size}`);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.game-logo {
|
||||||
|
display: inline-block;
|
||||||
|
object-fit: contain;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
image-rendering: -webkit-optimize-contrast;
|
||||||
|
image-rendering: crisp-edges;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-small {
|
||||||
|
max-width: 32px;
|
||||||
|
max-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-medium {
|
||||||
|
max-width: 64px;
|
||||||
|
max-height: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-large {
|
||||||
|
max-width: 128px;
|
||||||
|
max-height: 128px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-custom {
|
||||||
|
/* Size set via style prop */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hover effect for interactive contexts */
|
||||||
|
.game-logo:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
<button class="btn-back" @click="goHome" title="Volver al inicio">
|
<button class="btn-back" @click="goHome" title="Volver al inicio">
|
||||||
← <span class="label">Inicio</span>
|
← <span class="label">Inicio</span>
|
||||||
</button>
|
</button>
|
||||||
<h1><span class="emoji">📈</span> Leaderboard</h1>
|
<h1><GameLogo size="medium" /> Leaderboard</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="btn-collapse" @click="filtersCollapsed = !filtersCollapsed" :title="filtersCollapsed ? 'Mostrar filtros' : 'Ocultar filtros'">
|
<button class="btn-collapse" @click="filtersCollapsed = !filtersCollapsed" :title="filtersCollapsed ? 'Mostrar filtros' : 'Ocultar filtros'">
|
||||||
@@ -130,6 +130,7 @@ import { useRouter } from 'vue-router';
|
|||||||
import EventChart from '../components/EventChart.vue';
|
import EventChart from '../components/EventChart.vue';
|
||||||
import EventFilters from '../components/EventFilters.vue';
|
import EventFilters from '../components/EventFilters.vue';
|
||||||
import DataSourceSelector from '../components/DataSourceSelector.vue';
|
import DataSourceSelector from '../components/DataSourceSelector.vue';
|
||||||
|
import GameLogo from '../components/GameLogo.vue';
|
||||||
import { useEventFilters } from '../composables/useEventFilters';
|
import { useEventFilters } from '../composables/useEventFilters';
|
||||||
|
|
||||||
interface RoomInfo { roomId: string; metadata?: any; }
|
interface RoomInfo { roomId: string; metadata?: any; }
|
||||||
@@ -981,7 +982,7 @@ function downloadJSON() {
|
|||||||
.glass.light { background: rgba(255, 255, 255, 0.92); border: 1px solid rgba(229, 231, 235, 0.95); box-shadow: 0 18px 50px rgba(0,0,0,0.12), inset 0 1px 0 rgba(255,255,255,0.75); backdrop-filter: blur(18px) saturate(120%); -webkit-backdrop-filter: blur(18px) saturate(120%); border-radius: 16px; }
|
.glass.light { background: rgba(255, 255, 255, 0.92); border: 1px solid rgba(229, 231, 235, 0.95); box-shadow: 0 18px 50px rgba(0,0,0,0.12), inset 0 1px 0 rgba(255,255,255,0.75); backdrop-filter: blur(18px) saturate(120%); -webkit-backdrop-filter: blur(18px) saturate(120%); border-radius: 16px; }
|
||||||
|
|
||||||
.header { display:flex; align-items:center; justify-content:space-between; gap: 8px; flex-wrap: wrap; padding: 8px 10px; margin-bottom: 10px; }
|
.header { display:flex; align-items:center; justify-content:space-between; gap: 8px; flex-wrap: wrap; padding: 8px 10px; margin-bottom: 10px; }
|
||||||
.header h1 { margin: 0; font-size: 18px; line-height: 1.2; flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
.header h1 { margin: 0; font-size: 18px; line-height: 1.2; flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: flex; align-items: center; gap: 8px; }
|
||||||
.header-left { display:flex; align-items:center; gap: 10px; flex: 1 1 auto; min-width: 200px; }
|
.header-left { display:flex; align-items:center; gap: 10px; flex: 1 1 auto; min-width: 200px; }
|
||||||
.btn-back { background:#667eea; color:#fff; border:none; border-radius:6px; padding:6px 10px; font-weight:600; cursor:pointer; transition: all 0.3s ease; font-size: 12px; }
|
.btn-back { background:#667eea; color:#fff; border:none; border-radius:6px; padding:6px 10px; font-weight:600; cursor:pointer; transition: all 0.3s ease; font-size: 12px; }
|
||||||
.btn-back:hover { background:#5b6bda; transform: translateY(-1px); box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); }
|
.btn-back:hover { background:#5b6bda; transform: translateY(-1px); box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); }
|
||||||
|
|||||||
@@ -6,7 +6,9 @@
|
|||||||
← UUIDs
|
← UUIDs
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="title">🎮 Snatch Game</h1>
|
<h1 class="title">
|
||||||
|
<GameLogo size="large" /> Snatch Game
|
||||||
|
</h1>
|
||||||
<div class="subtitle">Arena de intercambio social</div>
|
<div class="subtitle">Arena de intercambio social</div>
|
||||||
|
|
||||||
<div class="player-section">
|
<div class="player-section">
|
||||||
@@ -35,7 +37,7 @@
|
|||||||
|
|
||||||
<div class="main-actions">
|
<div class="main-actions">
|
||||||
<button @click="handleQuickPlay" class="btn btn-primary btn-large" :disabled="isJoining || !nameConfirmed">
|
<button @click="handleQuickPlay" class="btn btn-primary btn-large" :disabled="isJoining || !nameConfirmed">
|
||||||
<span v-if="!isJoining">🧪 Juego Demo</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 v-if="!nameConfirmed" class="hint">Antes de jugar, presiona "Confirmar Nombre" para confirmar tu nombre.</div>
|
||||||
@@ -68,6 +70,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, computed, watch, nextTick } from 'vue';
|
import { ref, onMounted, onUnmounted, computed, watch, nextTick } from 'vue';
|
||||||
import PlayerStats from './games/PlayerStats.vue';
|
import PlayerStats from './games/PlayerStats.vue';
|
||||||
|
import GameLogo from '../components/GameLogo.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';
|
||||||
@@ -430,6 +433,10 @@ function goToSelector() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.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: 0;
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
<div class="uuid-selector-container">
|
<div class="uuid-selector-container">
|
||||||
<div class="selector-card">
|
<div class="selector-card">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h1 class="title">🎮 Snatch Game</h1>
|
<h1 class="title">
|
||||||
|
<GameLogo size="large" /> Snatch Game
|
||||||
|
</h1>
|
||||||
<p class="subtitle">Selecciona tu UUID para continuar</p>
|
<p class="subtitle">Selecciona tu UUID para continuar</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -97,7 +99,10 @@
|
|||||||
<div class="print-container" ref="printContainer">
|
<div class="print-container" ref="printContainer">
|
||||||
<div class="qr-print-page">
|
<div class="qr-print-page">
|
||||||
<div class="qr-header">
|
<div class="qr-header">
|
||||||
<h2>🎮 Snatch Game</h2>
|
<div class="header-title">
|
||||||
|
<img src="/SnatchGame.png?v=2" alt="SnatchGame" class="qr-logo" />
|
||||||
|
<h2>SnatchGame</h2>
|
||||||
|
</div>
|
||||||
<p class="player-info">{{ printModal.name || 'Jugador' }}</p>
|
<p class="player-info">{{ printModal.name || 'Jugador' }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="qr-code-container">
|
<div class="qr-code-container">
|
||||||
@@ -127,6 +132,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, nextTick } from 'vue';
|
import { ref, onMounted, nextTick } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
import GameLogo from '../components/GameLogo.vue';
|
||||||
import QRCode from 'qrcode';
|
import QRCode from 'qrcode';
|
||||||
import html2canvas from 'html2canvas';
|
import html2canvas from 'html2canvas';
|
||||||
|
|
||||||
@@ -299,6 +305,9 @@ async function printQR(uuidInfo: UuidInfo | null) {
|
|||||||
light: '#FFFFFF'
|
light: '#FFFFFF'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add logo to QR code
|
||||||
|
await addLogoToQR(qrCanvas.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,9 +353,25 @@ function executePrint() {
|
|||||||
.qr-header {
|
.qr-header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
.header-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.qr-logo {
|
||||||
|
max-width: 48px;
|
||||||
|
max-height: 48px;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
object-fit: contain;
|
||||||
|
image-rendering: -webkit-optimize-contrast;
|
||||||
|
image-rendering: crisp-edges;
|
||||||
|
}
|
||||||
.qr-header h2 {
|
.qr-header h2 {
|
||||||
font-size: 36px;
|
font-size: 36px;
|
||||||
margin: 0 0 10px 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
.player-info {
|
.player-info {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
@@ -441,11 +466,28 @@ async function downloadPNG() {
|
|||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
">
|
">
|
||||||
<div style="text-align: center;">
|
<div style="text-align: center;">
|
||||||
|
<div style="
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
">
|
||||||
|
<img src="/SnatchGame.png?v=2" style="
|
||||||
|
max-width: 48px;
|
||||||
|
max-height: 48px;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
object-fit: contain;
|
||||||
|
image-rendering: -webkit-optimize-contrast;
|
||||||
|
image-rendering: crisp-edges;
|
||||||
|
" alt="SnatchGame" />
|
||||||
<h2 style="
|
<h2 style="
|
||||||
font-size: 36px;
|
font-size: 36px;
|
||||||
margin: 0 0 10px 0;
|
margin: 0;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
">🎮 Snatch Game</h2>
|
">SnatchGame</h2>
|
||||||
|
</div>
|
||||||
<p style="
|
<p style="
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -540,6 +582,90 @@ function shareQR() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function to add logo to QR code with high quality
|
||||||
|
async function addLogoToQR(canvas: HTMLCanvasElement) {
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (!ctx) {
|
||||||
|
reject(new Error('Could not get canvas context'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable high-quality rendering
|
||||||
|
ctx.imageSmoothingEnabled = true;
|
||||||
|
ctx.imageSmoothingQuality = 'high';
|
||||||
|
|
||||||
|
const img = new Image();
|
||||||
|
img.crossOrigin = 'anonymous';
|
||||||
|
img.onload = () => {
|
||||||
|
try {
|
||||||
|
// Calculate logo size (about 18% of QR code size for better readability)
|
||||||
|
const qrSize = Math.min(canvas.width, canvas.height);
|
||||||
|
const logoSize = qrSize * 0.18;
|
||||||
|
|
||||||
|
// Center position
|
||||||
|
const centerX = canvas.width / 2;
|
||||||
|
const centerY = canvas.height / 2;
|
||||||
|
|
||||||
|
// Calculate aspect ratio preserving dimensions
|
||||||
|
const imgAspectRatio = img.naturalWidth / img.naturalHeight;
|
||||||
|
let logoWidth = logoSize;
|
||||||
|
let logoHeight = logoSize;
|
||||||
|
|
||||||
|
if (imgAspectRatio > 1) {
|
||||||
|
// Image is wider than tall
|
||||||
|
logoHeight = logoSize / imgAspectRatio;
|
||||||
|
} else {
|
||||||
|
// Image is taller than wide
|
||||||
|
logoWidth = logoSize * imgAspectRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
const logoX = centerX - logoWidth / 2;
|
||||||
|
const logoY = centerY - logoHeight / 2;
|
||||||
|
|
||||||
|
// Draw white background with rounded corners for better appearance
|
||||||
|
const padding = 8;
|
||||||
|
const bgWidth = logoWidth + padding * 2;
|
||||||
|
const bgHeight = logoHeight + padding * 2;
|
||||||
|
const bgX = centerX - bgWidth / 2;
|
||||||
|
const bgY = centerY - bgHeight / 2;
|
||||||
|
const cornerRadius = 6;
|
||||||
|
|
||||||
|
ctx.fillStyle = '#FFFFFF';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.roundRect(bgX, bgY, bgWidth, bgHeight, cornerRadius);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Add subtle shadow for depth
|
||||||
|
ctx.shadowColor = 'rgba(0, 0, 0, 0.1)';
|
||||||
|
ctx.shadowBlur = 4;
|
||||||
|
ctx.shadowOffsetX = 0;
|
||||||
|
ctx.shadowOffsetY = 2;
|
||||||
|
|
||||||
|
// Draw logo with high quality
|
||||||
|
ctx.drawImage(img, logoX, logoY, logoWidth, logoHeight);
|
||||||
|
|
||||||
|
// Reset shadow
|
||||||
|
ctx.shadowColor = 'transparent';
|
||||||
|
ctx.shadowBlur = 0;
|
||||||
|
ctx.shadowOffsetX = 0;
|
||||||
|
ctx.shadowOffsetY = 0;
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
img.onerror = () => {
|
||||||
|
console.warn('Failed to load logo for QR, continuing without it');
|
||||||
|
resolve(); // Continue without logo if it fails to load
|
||||||
|
};
|
||||||
|
|
||||||
|
img.src = '/SnatchGame.png?v=2';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function copyToClipboard() {
|
async function copyToClipboard() {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(printModal.value.url);
|
await navigator.clipboard.writeText(printModal.value.url);
|
||||||
@@ -588,6 +714,10 @@ async function copyToClipboard() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16px;
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
@@ -1033,8 +1163,26 @@ async function copyToClipboard() {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-logo {
|
||||||
|
max-width: 48px;
|
||||||
|
max-height: 48px;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
object-fit: contain;
|
||||||
|
image-rendering: -webkit-optimize-contrast;
|
||||||
|
image-rendering: crisp-edges;
|
||||||
|
}
|
||||||
|
|
||||||
.qr-header h2 {
|
.qr-header h2 {
|
||||||
margin: 0 0 10px 0;
|
margin: 0;
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user