/credits implementado en toda la app
This commit is contained in:
112
client/src/components/AppCredits.vue
Normal file
112
client/src/components/AppCredits.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<div class="app-credits" :class="[variantClass, positionClass]" aria-label="Créditos y contacto" ref="rootEl">
|
||||
<div class="credits-card" :class="{ collapsed }">
|
||||
<button v-if="!collapsed" class="btn-toggle close" @click="collapse" title="Ocultar">×</button>
|
||||
|
||||
<template v-if="!collapsed">
|
||||
<span>Hecho por <strong>Nucleo Inteligencia</strong></span>
|
||||
<span class="sep">•</span>
|
||||
<span>2025</span>
|
||||
<span class="sep">•</span>
|
||||
<a href="mailto:firstcontact@nucleoriofrio.com" class="credits-link">firstcontact@nucleoriofrio.com</a>
|
||||
<span class="sep">•</span>
|
||||
<span>Proyecto abierto, sin fines de lucro</span>
|
||||
<span class="sep">•</span>
|
||||
<RouterLink to="/credits" class="credits-link icon" title="Créditos detallados">ⓘ</RouterLink>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<button class="btn-toggle expand" @click="expand" title="Mostrar">◄</button>
|
||||
<RouterLink to="/credits" class="credits-link icon" title="Créditos detallados">ⓘ</RouterLink>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, onMounted, onUnmounted, nextTick } from 'vue';
|
||||
|
||||
interface Props {
|
||||
position?: 'bottom-right'|'bottom-center';
|
||||
variant?: 'overlay'|'inline';
|
||||
}
|
||||
const props = defineProps<Props>();
|
||||
const positionClass = computed(() => (props.variant === 'inline') ? '' : (props.position || 'bottom-right'));
|
||||
const variantClass = computed(() => props.variant === 'inline' ? 'inline' : 'overlay');
|
||||
const rootEl = ref<HTMLElement | null>(null);
|
||||
const collapsed = ref(false);
|
||||
|
||||
function setBottomGap() {
|
||||
if (variantClass.value !== 'overlay') return;
|
||||
try {
|
||||
const el = rootEl.value?.querySelector('.credits-card') as HTMLElement | null;
|
||||
const h = el ? el.offsetHeight : 0;
|
||||
const gap = h ? h + 12 : 0;
|
||||
document.documentElement.style.setProperty('--credits-gap', gap ? `${gap}px` : '0px');
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function collapse() { collapsed.value = true; setTimeout(setBottomGap, 0); }
|
||||
function expand() { collapsed.value = false; setTimeout(setBottomGap, 0); }
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick();
|
||||
setBottomGap();
|
||||
window.addEventListener('resize', setBottomGap);
|
||||
window.addEventListener('orientationchange', setBottomGap as any);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (variantClass.value === 'overlay') {
|
||||
document.documentElement.style.setProperty('--credits-gap', '0px');
|
||||
}
|
||||
window.removeEventListener('resize', setBottomGap);
|
||||
window.removeEventListener('orientationchange', setBottomGap as any);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.app-credits { z-index: 40; pointer-events: none; }
|
||||
.app-credits.overlay { position: fixed; }
|
||||
.app-credits.bottom-right { right: 12px; bottom: 12px; }
|
||||
.app-credits.bottom-center { left: 50%; transform: translateX(-50%); bottom: 12px; position: fixed; }
|
||||
.app-credits.inline { position: static; margin-top: 10px; display: flex; justify-content: flex-end; }
|
||||
|
||||
.credits-card {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 10px;
|
||||
font-size: 11.5px;
|
||||
line-height: 1.1;
|
||||
color: #394352;
|
||||
background: rgba(255, 255, 255, 0.322);
|
||||
backdrop-filter: blur(6px);
|
||||
-webkit-backdrop-filter: blur(6px);
|
||||
border: 1px solid rgba(0,0,0,0.06);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 6px 18px rgba(0,0,0,0.06);
|
||||
pointer-events: auto; /* allow clicking the mailto link */
|
||||
}
|
||||
.credits-card.collapsed { gap: 6px; padding: 4px 6px; }
|
||||
.credits-card.collapsed > span:not(.sep),
|
||||
.credits-card.collapsed > .sep,
|
||||
.credits-card.collapsed > .credits-link:not(.icon),
|
||||
.credits-card.collapsed > .btn-toggle.close { display: none; }
|
||||
.app-credits.inline .credits-card { background: rgba(255,255,255,0.14); border-color: rgba(0,0,0,0.04); }
|
||||
.credits-card strong { color: #475569; font-weight: 700; }
|
||||
.credits-card .sep { opacity: 0.55; }
|
||||
.credits-link { color: #64748b; text-decoration: none; border-bottom: 1px dotted rgba(100,116,139,0.45); }
|
||||
.credits-link:hover { color: #334155; border-bottom-color: rgba(51,65,85,0.55); }
|
||||
.credits-link.icon { border-bottom: none; display: inline-flex; align-items:center; justify-content:center; width: 18px; height: 18px; border-radius: 50%; background: rgba(255,255,255,0.6); color:#334155; font-weight: 700; font-size: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.08); }
|
||||
.credits-link.icon:hover { background: rgba(255,255,255,0.9); }
|
||||
|
||||
.btn-toggle { appearance: none; border: none; background: transparent; color:#475569; cursor: pointer; padding: 0; margin: 0; border-radius: 6px; }
|
||||
.btn-toggle.close { font-size: 14px; line-height: 1; margin-right: 4px; }
|
||||
.btn-toggle.expand { font-size: 12px; line-height: 1; margin-right: 4px; }
|
||||
.btn-toggle:focus { outline: none; }
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.credits-card { font-size: 11px; padding: 6px 8px; }
|
||||
}
|
||||
</style>
|
||||
@@ -5,6 +5,7 @@ import Dashboard from '../views/Dashboard.vue';
|
||||
import DemoGame from '../views/DemoGame.vue';
|
||||
import UuidSelector from '../views/UuidSelector.vue';
|
||||
import Leaderboard from '../views/Leaderboard.vue';
|
||||
import Credits from '../views/Credits.vue';
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
@@ -34,6 +35,11 @@ const router = createRouter({
|
||||
name: 'Leaderboard',
|
||||
component: Leaderboard
|
||||
},
|
||||
{
|
||||
path: '/credits',
|
||||
name: 'Credits',
|
||||
component: Credits
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
name: 'UuidSelector',
|
||||
|
||||
77
client/src/views/Credits.vue
Normal file
77
client/src/views/Credits.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<div class="credits-page">
|
||||
<div class="card">
|
||||
<div class="header">
|
||||
<button class="btn-back" @click="goBack" title="Volver">
|
||||
← Volver
|
||||
</button>
|
||||
<h1>Créditos de SnatchGame</h1>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<p>
|
||||
SnatchGame es un proyecto abierto y de uso público, sin fines de lucro.
|
||||
</p>
|
||||
<ul class="list">
|
||||
<li>
|
||||
Creado por <strong>Nucleo Inteligencia</strong>
|
||||
</li>
|
||||
<li>
|
||||
Año de creación: <strong>2024</strong>
|
||||
</li>
|
||||
<li>
|
||||
Contacto: <a href="mailto:firstcontact@nucleoriofrio.com">firstcontact@nucleoriofrio.com</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p class="note">
|
||||
Gracias por jugar y contribuir a la comunidad. Si querés colaborar, difundir o proponer mejoras, ¡escribinos!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router';
|
||||
const router = useRouter();
|
||||
function goBack() {
|
||||
if (window.history.length > 1) router.back();
|
||||
else router.push('/');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.credits-page {
|
||||
min-height: calc(var(--app-vh, 1vh) * 100);
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
.card {
|
||||
width: 100%;
|
||||
max-width: 780px;
|
||||
background: rgba(255,255,255,0.85);
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(0,0,0,0.06);
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.25);
|
||||
padding: 24px;
|
||||
}
|
||||
.header { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
|
||||
.btn-back { background: rgba(255,255,255,0.6); border: 1px solid rgba(0,0,0,0.08); border-radius: 8px; padding: 6px 10px; cursor: pointer; }
|
||||
.header h1 { margin: 0; font-size: 20px; color: #334155; }
|
||||
.content { color: #334155; }
|
||||
.list { margin: 12px 0; padding-left: 18px; }
|
||||
.list li { margin: 6px 0; }
|
||||
.note { margin-top: 12px; font-size: 14px; color: #475569; }
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.card { padding: 18px; }
|
||||
.header h1 { font-size: 18px; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -231,6 +231,7 @@
|
||||
@view-details="viewRoomDetails"
|
||||
@kick-player="kickPlayer"
|
||||
/>
|
||||
<AppCredits position="bottom-right" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -238,6 +239,7 @@
|
||||
import { ref, onMounted, onUnmounted, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { colyseusService } from '../services/colyseus';
|
||||
import AppCredits from '../components/AppCredits.vue';
|
||||
import RoomCard from '../components/RoomCard.vue';
|
||||
import RoomsTable from '../components/RoomsTable.vue';
|
||||
import RoomModal from '../components/RoomModal.vue';
|
||||
|
||||
@@ -72,8 +72,10 @@
|
||||
<div class="game-footer">
|
||||
<button @click="leaveGame" class="btn btn-leave">Salir del Juego</button>
|
||||
</div>
|
||||
<AppCredits variant="inline" />
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Pause overlay to block all interactions -->
|
||||
<div v-if="gameStatus === 'paused'" class="pause-overlay">
|
||||
<div class="pause-box">
|
||||
@@ -90,6 +92,7 @@ import { ref, onMounted, computed } from 'vue';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { colyseusService } from '../services/colyseus';
|
||||
import { getStateCallbacks } from 'colyseus.js';
|
||||
import AppCredits from '../components/AppCredits.vue';
|
||||
|
||||
import G1 from './games/G1.vue';
|
||||
import G2 from './games/G2.vue';
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
<div class="game-footer">
|
||||
<button @click="leaveGame" class="btn btn-leave">Leave Game</button>
|
||||
</div>
|
||||
<AppCredits variant="inline" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -78,6 +79,7 @@ import { ref, onMounted, onUnmounted, computed } from 'vue';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { colyseusService } from '../services/colyseus';
|
||||
import { getStateCallbacks } from 'colyseus.js';
|
||||
import AppCredits from '../components/AppCredits.vue';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
@@ -121,6 +121,7 @@
|
||||
totalPlayers: totalPlayersCount
|
||||
}"
|
||||
/>
|
||||
<AppCredits position="bottom-right" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -131,6 +132,7 @@ import EventChart from '../components/EventChart.vue';
|
||||
import EventFilters from '../components/EventFilters.vue';
|
||||
import DataSourceSelector from '../components/DataSourceSelector.vue';
|
||||
import GameLogo from '../components/GameLogo.vue';
|
||||
import AppCredits from '../components/AppCredits.vue';
|
||||
import { useEventFilters } from '../composables/useEventFilters';
|
||||
|
||||
interface RoomInfo { roomId: string; metadata?: any; }
|
||||
|
||||
@@ -78,6 +78,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AppCredits variant="inline" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -86,6 +88,7 @@
|
||||
import { ref, onMounted, onUnmounted, computed, watch, nextTick } from 'vue';
|
||||
import PlayerStats from './games/PlayerStats.vue';
|
||||
import GameLogo from '../components/GameLogo.vue';
|
||||
import AppCredits from '../components/AppCredits.vue';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { colyseusService } from '../services/colyseus';
|
||||
import { getStateCallbacks } from 'colyseus.js';
|
||||
@@ -969,3 +972,6 @@ margin: 0 0 20px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Credits overlay -->
|
||||
<AppCredits position="bottom-right" />
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
<div v-if="uuidInfo.color" class="color-indicator" :style="{ background: uuidInfo.color }"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- QR Mode Toggle -->
|
||||
<div class="qr-mode-container">
|
||||
<label class="qr-mode-label">
|
||||
@@ -98,6 +98,19 @@
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Credits -->
|
||||
<div class="credits" aria-label="Créditos y contacto">
|
||||
<div class="credits-card">
|
||||
<span>Hecho por <strong>Nucleo Inteligencia</strong></span>
|
||||
<span class="sep">•</span>
|
||||
<span>2024</span>
|
||||
<span class="sep">•</span>
|
||||
<a href="mailto:firstcontact@nucleoriofrio.com" class="credits-link">firstcontact@nucleoriofrio.com</a>
|
||||
<span class="sep">•</span>
|
||||
<span>Proyecto abierto, sin fines de lucro</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Context Menu -->
|
||||
@@ -926,6 +939,25 @@ async function copyToClipboard() {
|
||||
.uuid-card { -webkit-tap-highlight-color: transparent; outline: none; }
|
||||
.uuid-card:focus, .uuid-card:focus-visible { outline: none; }
|
||||
|
||||
/* Credits */
|
||||
.credits { display:flex; justify-content:center; margin-top: 14px; }
|
||||
.credits-card {
|
||||
background: rgba(255,255,255,0.22);
|
||||
backdrop-filter: blur(6px);
|
||||
-webkit-backdrop-filter: blur(6px);
|
||||
border: 1px solid rgba(0,0,0,0.04);
|
||||
border-radius: 10px;
|
||||
padding: 8px 12px;
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
line-height: 1.2;
|
||||
text-align: center;
|
||||
}
|
||||
.credits-card .sep { opacity: 0.6; margin: 0 6px; }
|
||||
.credits-card strong { color: #475569; font-weight: 700; }
|
||||
.credits-link { color: #64748b; text-decoration: none; border-bottom: 1px dotted rgba(100,116,139,0.5); }
|
||||
.credits-link:hover { color: #475569; border-bottom-color: rgba(71,85,105,0.6); }
|
||||
|
||||
.uuids-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
|
||||
Reference in New Issue
Block a user