bucket + card sharer
All checks were successful
Deploy App / build (push) Successful in 1m57s
Deploy App / deploy (push) Successful in 16s

This commit is contained in:
valere
2025-12-26 19:27:33 +01:00
parent d8fe645e5c
commit afb20fe75f
26 changed files with 1248 additions and 749 deletions

View File

@@ -1,27 +0,0 @@
<template>
<boxes />
</template>
<script setup>
import { useUiStore } from '~/store/ui'
import { useDataStore } from '~/store/data'
// Configuration du layout
definePageMeta({
layout: 'default'
})
const uiStore = useUiStore()
onMounted(async () => {
const dataStore = useDataStore()
await dataStore.loadData()
uiStore.listBoxes()
})
</script>
<style>
.logo {
filter: drop-shadow(3px 3px 0 rgb(0 0 0 / 0.7));
}
</style>

202
app/pages/card/[id].vue Normal file
View File

@@ -0,0 +1,202 @@
<template>
<div class="card-page">
<div v-if="loading" class="loading">
<div class="spinner"></div>
</div>
<Transition name="card-fade" mode="out-in">
<div v-if="!loading && track" class="card-container" @click="playTrack">
<Card :track="track" :is-face-up="true" class="card-item" />
</div>
<div v-else-if="error" class="error-message">
{{ error }}
</div>
</Transition>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue'
import { useRoute } from 'vue-router'
import { usePlayerStore } from '~/store/player'
import { useCardStore } from '~/store/card'
import { useDataStore } from '~/store/data'
import type { Track } from '~~/types/types'
const route = useRoute()
const playerStore = usePlayerStore()
const cardStore = useCardStore()
const dataStore = useDataStore()
const track = ref<Track | null>(null)
const loading = ref(true)
const error = ref<string | null>(null)
const hasUserInteracted = ref(false)
const audioElement = ref<HTMLAudioElement | null>(null)
// Récupérer les données de la piste
const fetchTrack = async () => {
try {
loading.value = true
error.value = null
// S'assurer que les données sont chargées
if (!dataStore.isLoaded) {
await dataStore.loadData()
}
// Récupérer la piste par son ID
const trackId = Array.isArray(route.params.id) ? route.params.id[0] : route.params.id || ''
const foundTrack = dataStore.getTrackById(trackId)
if (foundTrack) {
track.value = foundTrack
// Marquer la carte comme révélée
if (foundTrack.id) {
cardStore.revealCard(Number(foundTrack.id))
}
} else {
error.value = 'Carte non trouvée'
}
} catch (err) {
console.error('Erreur lors du chargement de la piste:', err)
error.value = 'Une erreur est survenue lors du chargement de la piste'
} finally {
loading.value = false
}
}
// Gérer la première interaction utilisateur
const handleFirstInteraction = () => {
if (!hasUserInteracted.value) {
hasUserInteracted.value = true
if (track.value) {
playerStore.playTrack(track.value)
}
}
}
// Configurer les écouteurs d'événements pour la première interaction
const setupInteractionListeners = () => {
const events: (keyof WindowEventMap)[] = ['click', 'touchstart', 'keydown']
const handleInteraction = () => {
handleFirstInteraction()
events.forEach(event => {
window.removeEventListener(event, handleInteraction as EventListener)
})
}
events.forEach(event => {
window.addEventListener(event, handleInteraction as EventListener, { once: true } as AddEventListenerOptions)
})
return () => {
events.forEach(event => {
window.removeEventListener(event, handleInteraction as EventListener)
})
}
}
// Lire la piste
const playTrack = () => {
if (track.value) {
playerStore.playTrack(track.value)
}
}
// Charger les données au montage du composant
onMounted(async () => {
await fetchTrack()
// Configurer les écouteurs d'événements pour la première interaction
const cleanup = setupInteractionListeners()
// Nettoyer les écouteurs lors du démontage du composant
onBeforeUnmount(() => {
cleanup()
})
})
</script>
<style scoped>
.card-page {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
background: linear-gradient(135deg, #1a202c 0%, #2d3748 100%);
}
.loading {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid rgba(255, 255, 255, 0.1);
border-radius: 50%;
border-top-color: #4299e1;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.card-container {
perspective: 1000px;
cursor: pointer;
transition: transform 0.3s ease;
}
.card-container:hover {
transform: scale(1.02);
}
.card-item {
transform-style: preserve-3d;
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
animation: cardAppear 0.8s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
opacity: 0;
transform: translateY(20px) rotateY(10deg);
}
@keyframes cardAppear {
0% {
opacity: 0;
transform: translateY(20px) rotateY(10deg);
}
100% {
opacity: 1;
transform: translateY(0) rotateY(0);
}
}
.error-message {
color: #feb2b2;
background-color: #742a2a;
padding: 1rem 2rem;
border-radius: 0.5rem;
font-weight: 500;
}
/* Animation de transition */
.card-fade-enter-active,
.card-fade-leave-active {
transition: opacity 0.3s ease;
}
.card-fade-enter-from,
.card-fade-leave-to {
opacity: 0;
}
</style>

View File

@@ -1,4 +1,27 @@
<template>
<card />
<card />
<boxes />
</template>
<script setup>
import { useUiStore } from '~/store/ui'
import { useDataStore } from '~/store/data'
// Configuration du layout
definePageMeta({
layout: 'default'
})
const uiStore = useUiStore()
onMounted(async () => {
const dataStore = useDataStore()
await dataStore.loadData()
uiStore.listBoxes()
})
</script>
<style>
.logo {
filter: drop-shadow(3px 3px 0 rgb(0 0 0 / 0.7));
}
</style>

View File

@@ -1,14 +0,0 @@
<template>
<div class="w-full flex flex-col items-center">
<logo />
<main>
<newsletter />
</main>
</div>
</template>
<style>
.logo {
filter: drop-shadow(2px 2px 0 rgb(0 0 0 / 0.8));
}
</style>

View File

@@ -1,221 +0,0 @@
<template>
<div class="min-h-screen bg-gradient-to-b from-slate-900 to-slate-800 p-8">
<h1 class="text-3xl font-bold text-center text-white mb-8">Card 3D Playground</h1>
<div class="container mx-auto flex flex-col lg:flex-row gap-8">
<!-- Controls Panel -->
<div class="w-full lg:w-1/3 bg-slate-800 p-6 rounded-xl shadow-lg">
<h2 class="text-xl font-semibold text-white mb-6">3D Controls</h2>
<div class="space-y-6">
<!-- Rotation -->
<div>
<h3 class="text-white font-medium mb-2">Rotation</h3>
<div class="space-y-4">
<div>
<label class="block text-sm text-gray-300 mb-1">X-Axis (rotateX)</label>
<input v-model="rotationX" type="range" min="-180" max="180" class="w-full" @input="updateTransform">
<span class="text-xs text-gray-400">{{ rotationX }}°</span>
</div>
<div>
<label class="block text-sm text-gray-300 mb-1">Y-Axis (rotateY)</label>
<input v-model="rotationY" type="range" min="-180" max="180" class="w-full" @input="updateTransform">
<span class="text-xs text-gray-400">{{ rotationY }}°</span>
</div>
<div>
<label class="block text-sm text-gray-300 mb-1">Z-Axis (rotateZ)</label>
<input v-model="rotationZ" type="range" min="-180" max="180" class="w-full" @input="updateTransform">
<span class="text-xs text-gray-400">{{ rotationZ }}°</span>
</div>
</div>
</div>
<!-- Translation -->
<div>
<h3 class="text-white font-medium mb-2">Position (px)</h3>
<div class="grid grid-cols-3 gap-4">
<div>
<label class="block text-xs text-gray-300 mb-1">X</label>
<input v-model="translateX" type="range" min="-200" max="200" class="w-full" @input="updateTransform">
<span class="text-xs text-gray-400">{{ translateX }}px</span>
</div>
<div>
<label class="block text-xs text-gray-300 mb-1">Y</label>
<input v-model="translateY" type="range" min="-200" max="200" class="w-full" @input="updateTransform">
<span class="text-xs text-gray-400">{{ translateY }}px</span>
</div>
<div>
<label class="block text-xs text-gray-300 mb-1">Z</label>
<input v-model="translateZ" type="range" min="-500" max="500" class="w-full" @input="updateTransform">
<span class="text-xs text-gray-400">{{ translateZ }}px</span>
</div>
</div>
</div>
<!-- Scale -->
<div>
<h3 class="text-white font-medium mb-2">Scale</h3>
<div class="grid grid-cols-3 gap-4">
<div>
<label class="block text-xs text-gray-300 mb-1">X</label>
<input v-model="scaleX" type="range" min="0.5" max="2" step="0.1" class="w-full"
@input="updateTransform">
<span class="text-xs text-gray-400">{{ scaleX }}x</span>
</div>
<div>
<label class="block text-xs text-gray-300 mb-1">Y</label>
<input v-model="scaleY" type="range" min="0.5" max="2" step="0.1" class="w-full"
@input="updateTransform">
<span class="text-xs text-gray-400">{{ scaleY }}x</span>
</div>
<div>
<label class="block text-xs text-gray-300 mb-1">Z</label>
<input v-model="scaleZ" type="range" min="0.5" max="2" step="0.1" class="w-full"
@input="updateTransform">
<span class="text-xs text-gray-400">{{ scaleZ }}x</span>
</div>
</div>
</div>
<!-- Reset Button -->
<button
class="w-full mt-6 bg-esyellow hover:bg-yellow-500 text-black font-medium py-2 px-4 rounded-md transition-colors"
@click="resetTransforms">
Reset to Default
</button>
</div>
</div>
<!-- Card Preview -->
<div class="flex-1 flex items-center justify-center min-h-[60vh] lg:min-h-auto">
<div class="relative w-64 h-96 transition-transform duration-300" :style="cardStyle">
<div class="w-full h-full" :style="{
transform: `
perspective(1000px)
rotateX(${rotationX}deg)
rotateY(${rotationY}deg)
rotateZ(${rotationZ}deg)
translate3d(${translateX}px, ${translateY}px, ${translateZ}px)
scale3d(${scaleX}, ${scaleY}, ${scaleZ})
`,
transformStyle: 'preserve-3d',
transition: 'transform 0.3s ease-out',
willChange: 'transform',
backfaceVisibility: 'visible'
}">
<div
class="w-full h-full bg-gradient-to-br from-cyan-500 to-blue-600 rounded-xl shadow-2xl flex items-center justify-center p-4">
<div class="text-center text-white">
<div class="text-6xl mb-2">🃏</div>
<h3 class="text-xl font-bold">Card Playground</h3>
<p class="text-sm opacity-80">Drag the sliders to transform me!</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
// State for 3D transformations
import { useDataStore } from '~/store/data'
const rotationX = ref(0)
const rotationY = ref(0)
const rotationZ = ref(0)
const translateX = ref(0)
const translateY = ref(0)
const translateZ = ref(0)
const scaleX = ref(1)
const scaleY = ref(1)
const scaleZ = ref(1)
const dataStore = useDataStore()
// Computed property for the card's transform style
const cardStyle = computed(() => {
return {
transform: `
perspective(1000px)
rotateX(${rotationX.value}deg)
rotateY(${rotationY.value}deg)
rotateZ(${rotationZ.value}deg)
translate3d(${translateX.value}px, ${translateY.value}px, ${translateZ.value}px)
scale3d(${scaleX.value}, ${scaleY.value}, ${scaleZ.value})
`,
transformStyle: 'preserve-3d',
transition: 'transform 0.3s ease-out',
willChange: 'transform',
backfaceVisibility: 'visible'
}
})
// Function to update transform (for immediate feedback)
const updateTransform = () => {
// The computed property will handle the update
}
// Function to reset all transforms
const resetTransforms = () => {
rotationX.value = 0
rotationY.value = 0
rotationZ.value = 0
translateX.value = 0
translateY.value = 0
translateZ.value = 0
scaleX.value = 1
scaleY.value = 1
scaleZ.value = 1
}
dataStore.isLoading.value = false
// Set page metadata
definePageMeta({
layout: 'default'
})
</script>
<style scoped>
/* Add any additional styles here */
.container {
perspective: 1000px;
}
/* Style range inputs */
input[type="range"] {
-webkit-appearance: none;
width: 100%;
height: 6px;
border-radius: 3px;
background: #475569;
outline: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: #f59e0b;
cursor: pointer;
transition: all 0.2s;
}
input[type="range"]::-webkit-slider-thumb:hover {
background: #d97706;
transform: scale(1.2);
}
/* Card styling */
.card-3d {
transform-style: preserve-3d;
transition: transform 0.3s ease-out;
will-change: transform;
backface-visibility: visible;
}
</style>

3
app/pages/story/holo.vue Normal file
View File

@@ -0,0 +1,3 @@
<template>
holo
</template>

46
app/pages/story/index.vue Normal file
View File

@@ -0,0 +1,46 @@
<template>
<div class="container">
<h1>Liste des pages Story</h1>
<ul>
<li v-for="page in pages" :key="page.name">
<NuxtLink :to="`/story/${page.name}`">
{{ page.name }}
</NuxtLink>
</li>
</ul>
</div>
</template>
<script setup>
const pages = [
{ name: 'holo' },
{ name: 'mix' },
{ name: 'test' }
]
</script>
<style scoped>
.container {
max-width: 800px;
margin: 0 auto;
padding: 2rem;
}
ul {
list-style: none;
padding: 0;
}
li {
margin: 0.5rem 0;
}
a {
color: #2563eb;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
</style>

4
app/pages/story/test.vue Normal file
View File

@@ -0,0 +1,4 @@
<template>
<card />
<card />
</template>