fix: refactored the codebase

This commit is contained in:
2026-08-20 23:00:47 +02:00
parent 6f47ed0674
commit a77f74911d
64 changed files with 2864 additions and 1739 deletions
+252
View File
@@ -0,0 +1,252 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import RoomModeratorControls from '@/components/rooms/RoomModeratorControls.vue';
import RoomSeat from '@/components/rooms/RoomSeat.vue';
import { usePaperBallToss } from '@/composables/usePaperBallToss';
import type {
PositionedRoomPlayer,
RoomTableProps,
SeatStyle,
} from '@/types/room';
const props = defineProps<RoomTableProps>();
const gameBoard = ref<HTMLElement | null>(null);
const { activePaperBalls, hitPlayerIds, throwingAtPlayerId, throwPaperBall } =
usePaperBallToss({
roomId: () => props.room.id,
currentPlayerId: () => props.currentPlayer.id,
board: gameBoard,
});
const positionedPlayers = computed<PositionedRoomPlayer[]>(() => {
const currentPlayerIndex = props.room.players.findIndex(
(player) => player.id === props.currentPlayer.id,
);
const orderedPlayers =
currentPlayerIndex === -1
? props.room.players
: [
...props.room.players.slice(currentPlayerIndex),
...props.room.players.slice(0, currentPlayerIndex),
];
const playerCount = Math.max(orderedPlayers.length, 1);
return orderedPlayers.map((player, index) => {
const angle = Math.PI / 2 + (index * Math.PI * 2) / playerCount;
const horizontalRadius = 39;
const verticalRadius = 40;
const cardDistance = 96;
return {
...player,
isTopSeat: Math.sin(angle) < -0.25,
seatStyle: {
left: `${50 + Math.cos(angle) * horizontalRadius}%`,
top: `${48 + Math.sin(angle) * verticalRadius}%`,
'--card-x': `${-Math.cos(angle) * cardDistance}px`,
'--card-y': `${-Math.sin(angle) * cardDistance}px`,
} as SeatStyle,
};
});
});
const voteCount = computed(
() => props.room.players.filter((player) => player.has_voted).length,
);
</script>
<template>
<section class="mt-5 w-full">
<div ref="gameBoard" class="game-board" aria-label="Scrum-Poker-Tisch">
<div class="poker-table" aria-hidden="true">
<div class="table-highlight"></div>
<div class="table-center">
<span
v-if="!room.revealed"
class="text-sm font-medium text-emerald-50/75"
>
{{ voteCount }} von {{ room.players.length }} haben
gewählt
</span>
<div v-else class="grid grid-cols-2 gap-8 text-emerald-50">
<div class="flex flex-col gap-1">
<span class="text-xs text-emerald-100/65">
Durchschnitt
</span>
<strong class="text-xl">
{{ room.statistics?.average ?? '–' }}
</strong>
</div>
<div class="flex flex-col gap-1">
<span class="text-xs text-emerald-100/65">
Median
</span>
<strong class="text-xl">
{{ room.statistics?.median ?? '–' }}
</strong>
</div>
</div>
</div>
</div>
<div
v-for="paperBall in activePaperBalls"
:key="paperBall.id"
class="paper-ball-flight"
:style="paperBall.style"
aria-hidden="true"
>
<span class="paper-ball"></span>
</div>
<RoomSeat
v-for="player in positionedPlayers"
:key="player.id"
:player="player"
:current-player-id="currentPlayer.id"
:current-vote="currentPlayer.current_vote"
:cards="room.deck.cards"
:revealed="room.revealed"
:is-paper-ball-hit="hitPlayerIds.includes(player.id)"
:throw-disabled="throwingAtPlayerId !== null"
@throw-paper-ball="throwPaperBall"
/>
<RoomModeratorControls
v-if="currentPlayer.is_moderator"
:room-id="room.id"
:revealed="room.revealed"
:has-votes="voteCount > 0"
/>
</div>
</section>
</template>
<style scoped>
.game-board {
position: relative;
width: 100%;
min-height: max(34rem, calc(100svh - 11rem));
isolation: isolate;
}
.poker-table {
position: absolute;
inset: 20% 15% 21%;
overflow: hidden;
border: 0.65rem solid #513b2d;
border-radius: 50%;
background:
radial-gradient(
circle at 50% 42%,
rgb(16 126 91 / 38%),
transparent 45%
),
linear-gradient(145deg, #136c54, #074c3d);
box-shadow:
inset 0 0 0 0.2rem rgb(255 255 255 / 8%),
inset 0 -1.5rem 3rem rgb(0 0 0 / 22%),
0 1.5rem 3rem rgb(0 0 0 / 35%);
}
.table-highlight {
position: absolute;
inset: 6%;
border: 1px solid rgb(255 255 255 / 8%);
border-radius: 50%;
}
.table-center {
position: absolute;
inset: 0;
display: grid;
place-items: center;
padding: 2rem;
text-align: center;
}
.paper-ball-flight {
position: absolute;
z-index: 40;
width: 1.65rem;
height: 1.65rem;
pointer-events: none;
will-change: transform;
animation: paper-ball-flight 780ms linear forwards;
}
.paper-ball {
display: block;
width: 100%;
height: 100%;
border: 1px solid rgb(100 116 139 / 65%);
border-radius: 48% 42% 54% 39%;
background:
linear-gradient(
35deg,
transparent 44%,
rgb(100 116 139 / 35%) 46% 51%,
transparent 53%
),
linear-gradient(
120deg,
transparent 35%,
rgb(148 163 184 / 45%) 37% 42%,
transparent 44%
),
#e2e8f0;
}
@keyframes paper-ball-flight {
0% {
transform: translate(-50%, -50%) translate(0, 0) rotate(0deg)
scale(0.72);
}
25% {
transform: translate(-50%, -50%)
translate(var(--travel-quarter-x), var(--travel-quarter-y))
rotate(195deg) scale(0.92);
}
50% {
transform: translate(-50%, -50%)
translate(var(--travel-mid-x), var(--travel-mid-y)) rotate(390deg)
scale(1.08);
}
75% {
transform: translate(-50%, -50%)
translate(
var(--travel-three-quarter-x),
var(--travel-three-quarter-y)
)
rotate(585deg) scale(1);
}
100% {
transform: translate(-50%, -50%)
translate(var(--travel-x), var(--travel-y)) rotate(780deg)
scale(0.82);
}
}
@media (max-width: 640px) {
.game-board {
min-height: max(28rem, calc(100svh - 10rem));
}
.poker-table {
inset: 24% 17% 23%;
border-width: 0.45rem;
}
}
@media (prefers-reduced-motion: reduce) {
.paper-ball-flight {
animation-duration: 1ms;
}
}
</style>