feat: FRO-26 Create Tie selection component

This commit is contained in:
2026-01-14 10:35:27 +01:00
parent f29ed9d338
commit dbffce8818

View File

@@ -1,10 +1,9 @@
<script setup lang="ts">
import {useWebSocket} from "@/composables/useWebsocket.ts";
import {useIngame} from "@/composables/useIngame.ts";
import type {GameInfo, LobbyInfo, TieInfo, TrumpInfo} from "@/types/GameTypes.ts";
import type { TieInfo } from "@/types/GameTypes.ts";
import {useQuasar} from "quasar";
import { ref } from 'vue';
import { computed } from 'vue'
import { computed, nextTick, ref } from 'vue'
const wb = useWebSocket()
const wi = useIngame()
@@ -35,6 +34,8 @@ const myRevealedCard = computed(() => {
});
const isFlipping = ref(false);
// Used to force-remount the flipping subtree so the CSS transition/animation restarts reliably
const revealKey = ref(0);
// This replaces your v-if check.
// It keeps the "Pick" screen visible while flipping.
@@ -46,13 +47,16 @@ const showPickScreen = computed(() => {
function selectTie(tieIndex: number) {
// Use model.value because it's a ref
wb.sendAndWait("PickTie", { cardIndex: tieIndex })
.then(() => {
.then(async () => {
console.log("Server accepted pick, starting animation...");
// 1. Trigger the animation state
isFlipping.value = true;
// 2. Force a remount of the flip-card for the selected card so the flip restarts even if src doesn't change
revealKey.value += 1;
await nextTick();
// 2. Optional Notification
// 3. Optional Notification
$q.notify({
message: "Card revealed!",
color: "positive",
@@ -60,11 +64,11 @@ function selectTie(tieIndex: number) {
timeout: 1000
});
// 3. Wait for animation to finish before switching to Waiting Screen
// 4. Wait for animation to finish before switching to Waiting Screen
setTimeout(() => {
isFlipping.value = false;
model.value = null;
}, 2500);
}, 500);
})
.catch((error) => {
console.error("Pick failed:", error);
@@ -114,12 +118,34 @@ const options = computed(() => {
:class="{ 'selected-card': model === n, 'is-flipping': isFlipping && model === n }"
>
<div class="card-inner">
<!-- Flip card: keep both faces mounted; flip the inner wrapper. -->
<div
class="flip-card"
:class="{ 'flip-card--flipped': isFlipping && model === n && !!myRevealedCard }"
:key="(isFlipping && model === n) ? `${revealKey}-${n}` : `static-${n}`"
>
<div class="flip-card__inner">
<!-- FRONT: blank card (deck back) -->
<div class="flip-card__face flip-card__front">
<q-img
:src="getCardImagePath(tieBlankCard)"
class="card-image shadow-24"
no-spinner
no-transition
/>
</div>
<!-- BACK: revealed card (your pick result) -->
<div class="flip-card__face flip-card__back">
<q-img
:key="myRevealedCard || 'hidden'"
:src="(model === n && myRevealedCard) ? myRevealedCard : getCardImagePath(tieBlankCard)"
class="card-image shadow-24"
:class="{ 'animate-reveal': isFlipping && model === n }"
no-spinner
no-transition
/>
</div>
</div>
</div>
<div v-if="!isFlipping" class="card-number">
{{ n }}
@@ -358,38 +384,55 @@ const options = computed(() => {
}
.card-inner {
perspective: 1000px;
transform-style: preserve-3d;
}
/* The Reveal Animation */
.animate-reveal {
animation: dramaticFlip 1.2s cubic-bezier(0.4, 0, 0.2, 1) forwards;
/* --- Flip card (reliable 3D flip) --- */
.flip-card {
width: 60px;
/* height follows image; q-img sets its own box, but we want a stable 3D container */
perspective: 1000px;
}
.flip-card__inner {
position: relative;
width: 100%;
transform-style: preserve-3d;
transition: transform 1.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.flip-card__face {
position: absolute;
inset: 0;
backface-visibility: hidden;
z-index: 1000;
}
@keyframes dramaticFlip {
0% {
transform: rotateY(0deg) scale(1);
}
40% {
/* Halfway: Card is vertical and lifted up */
transform: rotateY(90deg) scale(1.5) translateY(-30px);
filter: brightness(1.3);
}
100% {
/* Final: Card is flat again, showing the face */
transform: rotateY(0deg) scale(1.3) translateY(0);
}
/* Ensure the q-img root stretches to fill the face */
.flip-card__face :deep(.q-img) {
width: 100%;
}
/* Ensure the image isn't mirrored after the rotation logic */
.animate-reveal :deep(img) {
backface-visibility: hidden;
.flip-card__front {
transform: rotateY(0deg);
}
.flip-card__back {
transform: rotateY(180deg);
}
.flip-card--flipped .flip-card__inner {
/* Lift/scale a bit like your original dramatic flip */
transform: rotateY(180deg) scale(1.3) translateY(-10px);
}
/* While flipping, don't let hover/selected transforms fight the 3D transform */
.tie-card-wrapper.is-flipping:hover {
transform: none;
}
/* Optional: Slight pulse while the button is waiting to be clicked */
.selected-card:not(.is-flipping) {
animation: pulse 2s infinite;
animation: pulse 0.5s infinite;
}
@keyframes pulse {