Files
evilspins/app/pages/dev.vue
valere 34d22b3b17
All checks were successful
Deploy App / build (push) Successful in 43s
Deploy App / deploy (push) Successful in 41s
evilSpins v1
2025-11-04 22:41:41 +01:00

191 lines
4.3 KiB
Vue

<template>
<div>
<button
class="fixed bottom-4 right-4 bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-full shadow-lg z-50 transition-colors"
title="Ranger les cartes"
@click="arrangeCards"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16m-7 6h7"
/>
</svg>
</button>
<div ref="deck" class="deck">
<card
v-for="track in tracks"
:key="track.id"
:track="track"
:class="['card', 'id-' + track.id, { dragging: dragging === track }]"
:style="{
top: track.y + 'px',
left: track.x + 'px',
zIndex: track.zIndex || 1,
transform: `rotate(${track.rotation || 0}deg)`
}"
:is-face-up="track.isFaceUp"
@mousedown="startDrag($event, track)"
@click="flipCard(track)"
/>
</div>
</div>
</template>
<script setup>
import { useDataStore } from '~/store/data'
const tracks = ref([])
const deck = ref(null)
let zIndexCounter = 1
let dragging = null
let offset = { x: 0, y: 0 }
let velocity = { x: 0, y: 0 }
let lastTime = 0
const dataStore = useDataStore()
definePageMeta({ layout: 'default' })
onMounted(async () => {
await dataStore.loadData()
tracks.value = dataStore.getTracksByboxId('ES2025').map((t, i) => ({
...t,
x: t.x ?? 50 + i * 20,
y: t.y ?? 50 + i * 20,
zIndex: t.zIndex ?? i + 1,
rotation: 0,
isFaceUp: t.isFaceUp ?? false
}))
window.addEventListener('mousemove', onDrag)
window.addEventListener('mouseup', stopDrag)
})
onBeforeUnmount(() => {
window.removeEventListener('mousemove', onDrag)
window.removeEventListener('mouseup', stopDrag)
})
function startDrag(e, track) {
dragging = track
const rect = deck.value.getBoundingClientRect()
offset.x = e.clientX - rect.left - track.x
offset.y = e.clientY - rect.top - track.y
lastTime = performance.now()
velocity = { x: 0, y: 0 }
zIndexCounter += 1
track.zIndex = zIndexCounter
}
function onDrag(e) {
if (!dragging) return
const rect = deck.value.getBoundingClientRect()
const newX = e.clientX - rect.left - offset.x
const newY = e.clientY - rect.top - offset.y
const now = performance.now()
const dt = now - lastTime
velocity.x = (newX - dragging.x) / dt
velocity.y = (newY - dragging.y) / dt
lastTime = now
dragging.x = newX
dragging.y = newY
// Rotation dynamique selon la position horizontale
const centerX = rect.width / 2
const dx = dragging.x - centerX
dragging.rotation = dx * 0.05
}
function stopDrag() {
if (!dragging) return
const track = dragging
dragging = null
// Inertie douce
const decay = 0.95
function animateInertia() {
if (Math.abs(velocity.x) < 0.02 && Math.abs(velocity.y) < 0.02) return
track.x += velocity.x * 16
track.y += velocity.y * 16
velocity.x *= decay
velocity.y *= decay
requestAnimationFrame(animateInertia)
}
animateInertia()
}
function flipCard(track) {
track.isFaceUp = true
}
function arrangeCards() {
const deckRect = deck.value.getBoundingClientRect()
const cardWidth = 224
const cardHeight = 320
const padding = 20
const cardsPerRow = Math.max(
1,
Math.floor((deckRect.width - padding * 2) / (cardWidth + padding))
)
tracks.value.forEach((track, index) => {
const row = Math.floor(index / cardsPerRow)
const col = index % cardsPerRow
track.x = padding + col * (cardWidth + padding)
track.y = padding + row * (cardHeight / 3)
track.zIndex = index + 1
track.rotation = 0
zIndexCounter = Math.max(zIndexCounter, track.zIndex)
})
}
</script>
<style lang="scss" scoped>
.deck {
position: relative;
width: 100%;
min-height: 80vh;
height: 100%;
overflow-y: auto;
padding: 1rem;
box-sizing: border-box;
}
.card {
position: absolute;
cursor: grab;
transition:
transform 0.1s ease,
box-shadow 0.2s ease;
}
.card:active {
cursor: grabbing;
}
.card.dragging {
z-index: 999;
cursor: grabbing;
transform: scale(1.05) rotate(0deg);
/* rotation sera remplacée dynamiquement */
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.5);
}
</style>