PWA fixed

This commit is contained in:
cpu
2025-05-08 15:36:17 +02:00
parent d741efa62d
commit 13b227cfc2
40 changed files with 5117 additions and 2 deletions

View File

@@ -0,0 +1,146 @@
<template>
<div
:class="['player-area p-2 md:p-4 flex flex-col items-center justify-center text-center relative no-select',
areaClass,
{ 'opacity-50': player.isSkipped,
'bg-green-100 dark:bg-green-800 animate-pulsePositive': player.isTimerRunning && player.currentTimerSec >=0 && !isNextPlayerArea,
'bg-red-100 dark:bg-red-900': player.isTimerRunning && player.currentTimerSec < 0 && !isNextPlayerArea,
}]"
@click="handleTap"
v-touch:swipe.up="handleSwipeUp"
>
<!-- Avatar -->
<div
class="mb-4 md:mb-4 relative"
:style="{
width: avatarSize.width,
height: avatarSize.height,
}"
>
<img
v-if="player.avatar"
:src="player.avatar"
alt="Player Avatar"
class="rounded-full object-cover border-2 md:border-4 shadow-lg w-full h-full"
:class="player.isSkipped ? 'border-gray-500 filter grayscale' : 'border-blue-500 dark:border-blue-400'"
/>
<DefaultAvatarIcon
v-else
class="rounded-full object-cover border-2 md:border-4 shadow-lg text-gray-400 dark:text-gray-500 bg-gray-200 dark:bg-gray-700 p-1 md:p-2 w-full h-full"
:class="player.isSkipped ? 'border-gray-500 filter grayscale !text-gray-300 dark:!text-gray-600' : 'border-blue-500 dark:border-blue-400'"
/>
</div>
<!-- Player Name -->
<h2 class="font-semibold mb-6 md:mb-6 text-5xl sm:text-2xl md:text-3xl lg:text-5xl">
{{ player.name }}
</h2>
<TimerDisplay
:seconds="player.currentTimerSec"
:is-negative="player.currentTimerSec < 0"
:is-pulsating="player.isTimerRunning"
class="text-4xl sm:text-5xl md:text-6xl lg:text-6xl xl:text-5xl"
/>
<p v-if="player.isSkipped" class="text-red-500 dark:text-red-400 mt-1 md:mt-2 font-semibold text-sm md:text-base lg:text-lg">SKIPPED</p>
<p v-if="isNextPlayerArea && !player.isSkipped" class="mt-1 md:mt-2 text-xs md:text-sm text-gray-600 dark:text-gray-400">(Swipe up to pass turn)</p>
</div>
</template>
<script setup>
import { computed, ref, onMounted, onUnmounted } from 'vue';
import TimerDisplay from './TimerDisplay.vue';
import DefaultAvatarIcon from './DefaultAvatarIcon.vue';
const vTouch = {
mounted: (el, binding) => {
if (binding.arg === 'swipe' && binding.modifiers.up) {
let touchstartX = 0;
let touchstartY = 0;
let touchendX = 0;
let touchendY = 0;
const swipeThreshold = 50;
el.addEventListener('touchstart', function(event) {
touchstartX = event.changedTouches[0].screenX;
touchstartY = event.changedTouches[0].screenY;
}, { passive: true });
el.addEventListener('touchend', function(event) {
touchendX = event.changedTouches[0].screenX;
touchendY = event.changedTouches[0].screenY;
handleGesture();
}, { passive: true });
function handleGesture() {
const deltaY = touchstartY - touchendY;
const deltaX = Math.abs(touchendX - touchstartX);
if (deltaY > swipeThreshold && deltaY > deltaX) {
if (typeof binding.value === 'function') {
binding.value();
}
}
}
}
}
};
const props = defineProps({
player: {
type: Object,
required: true
},
isCurrentPlayerArea: Boolean,
isNextPlayerArea: Boolean,
areaClass: String,
});
const emit = defineEmits(['tapped', 'swiped-up']);
const avatarSize = ref({ width: '120px', height: '120px' });
const calculateAvatarSize = () => {
const screenWidth = window.innerWidth;
const screenHeight = window.innerHeight;
const isNormalModeContext = document.querySelector('.player-area')?.parentElement?.classList.contains('flex-col');
if (isNormalModeContext) {
const availableHeight = screenHeight / 2;
let size = Math.min(availableHeight * 0.5, screenWidth * 0.4, 175);
if (screenWidth < 960) {
size = Math.min(availableHeight * 0.7, screenWidth * 0.6, 250);
} else if (screenWidth < 1024) {
size = Math.min(availableHeight * 0.55, screenWidth * 0.45, 180);
}
size = Math.max(size, 100);
avatarSize.value = { width: `${size}px`, height: `${size}px` };
} else {
avatarSize.value = { width: '120px', height: '120px' };
}
};
onMounted(() => {
calculateAvatarSize();
window.addEventListener('resize', calculateAvatarSize);
});
onUnmounted(() => {
window.removeEventListener('resize', calculateAvatarSize);
});
const handleTap = () => {
if (props.isCurrentPlayerArea && !props.player.isSkipped) {
emit('tapped');
}
};
const handleSwipeUp = () => {
if (props.isNextPlayerArea && !props.player.isSkipped) {
emit('swiped-up');
}
};
</script>