animations + cards
All checks were successful
Deploy App / deploy (push) Successful in 30s

This commit is contained in:
valere
2025-09-30 01:10:12 +02:00
parent 631bc65c70
commit 9438394db8
20 changed files with 775 additions and 455 deletions

View File

@@ -6,18 +6,11 @@
</template>
<script setup>
import { useDataStore } from '@/store/dataStore'
useHead({
bodyAttrs: {
class: 'bg-slate-100 dark:bg-slate-900'
}
})
// @todo : load datas as plugin/middleware (cant load pinia in plugin/middleware) ?
onMounted(async () => {
const dataStore = await useDataStore()
await dataStore.loadData()
})
</script>
<style>

View File

@@ -1,327 +0,0 @@
<template>
<!-- scène 3D -->
<div ref="scene" class="scene z-10">
<div ref="box" class="box">
<div class="face front relative" ref="frontFace">
<img class="cover absolute" :src="`/${compilation.id}/cover.jpg`" alt="">
</div>
<div class="face back" ref="backFace" />
<div class="face right" ref="rightFace" />
<div class="face left" ref="leftFace" />
<div class="face top" ref="topFace">
<img class="logo h-full p-1" src="/logo.svg" alt="">
<img class="absolute block h-1/2" style="left:5%;" :src="`/${compilation.id}/title.svg`" alt="">
</div>
<div class="face bottom" ref="bottomFace" />
</div>
</div>
</template>
<script setup lang="ts">
import type { Compilation, BoxPosition } from '~~/types/types';
const props = withDefaults(
defineProps<{
compilation: Compilation
position?: BoxPosition
size?: number
}>(),
{
position: () => ({ x: 0, y: 0, z: 0 }),
size: 6,
}
)
// States
const angleX = ref(props.position.x)
const angleY = ref(props.position.y)
const angleZ = ref(props.position.z)
const frontFace = ref()
const backFace = ref()
const rightFace = ref()
const leftFace = ref()
const topFace = ref()
const bottomFace = ref()
const box = ref()
const scene = ref()
/*
ÉTATS POUR LE DRAG + INERTIE
*/
let dragging = false
let lastPointer = { x: 0, y: 0, time: 0 } // position précédente du pointeur
let velocity = { x: 0, y: 0 } // vitesse calculée pour inertie
let raf = null // id du requestAnimationFrame
/*
PARAMÈTRES DE RÉGLAGE
*/
const sensitivity = 0.3 // combien de degrés par pixel de mouvement
const friction = 0.95 // inertie : 1 = sans perte, plus bas = ralentit vite
const minVelocity = 0.02 // seuil sous lequel on arrête linertie
const enableInertia = true // true = inertie activée, false = rotation immédiate sans suite
/*
Applique la transformation CSS à la box
*/
function applyTransform() {
angleX.value = Math.round(angleX.value)
angleY.value = Math.round(angleY.value)
angleZ.value = Math.round(angleZ.value)
box.value.style.transform = `rotateX(${angleX.value}deg) rotateY(${angleY.value}deg) rotateZ(${angleZ.value}deg)`
}
function applySize() {
updateCssVar('--height', `${props.size * (100 / 3)}px`, scene.value)
updateCssVar('--width', `${props.size * 50}px`, scene.value)
updateCssVar('--depth', `${props.size * 10}px`, scene.value)
}
function applyColor() {
frontFace.value.style.setProperty('background', `${props.compilation.color2}`)
backFace.value.style.setProperty('background', `linear-gradient(to top, ${props.compilation.color1}, ${props.compilation.color2})`)
rightFace.value.style.setProperty('background', `linear-gradient(to top, ${props.compilation.color1}, ${props.compilation.color2})`)
leftFace.value.style.setProperty('background', `linear-gradient(to top, ${props.compilation.color1}, ${props.compilation.color2})`)
topFace.value.style.setProperty('background', `linear-gradient(to top, ${props.compilation.color2}, ${props.compilation.color2})`)
bottomFace.value.style.setProperty('background', `${props.compilation.color1}`)
}
/*
Fonction utilitaire : place la box directement à une rotation donnée
*/
function applyRotation() {
angleX.value = props.position.x
angleY.value = props.position.y
angleZ.value = props.position.z
applyTransform()
box.value.style.setProperty('transition', 'transform 800ms ease-in-out')
setTimeout(() => {
box.value.style.setProperty('transition', 'transform 120ms linear')
}, 120)
}
/*
Boucle dinertie après un drag
- reprend la vitesse calculée à la release
- diminue petit à petit avec friction
- stoppe quand vitesse < minVelocity
*/
function tickInertia() {
if (!enableInertia) return
// appliquer la friction
velocity.x *= friction
velocity.y *= friction
// appliquer au box
angleX.value += velocity.y
angleY.value += velocity.x
// clamp angleX pour éviter de retourner la box trop loin
angleX.value = Math.max(-80, Math.min(80, angleX.value))
applyTransform()
// continuer tant quil reste du mouvement
if (Math.abs(velocity.x) > minVelocity || Math.abs(velocity.y) > minVelocity) {
raf = requestAnimationFrame(tickInertia)
} else {
raf = null
}
}
/*
Mise en place des listeners une fois le composant monté
*/
onMounted(() => {
applySize()
applyColor()
applyTransform()
// pointerdown = début du drag
const down = (ev) => {
ev.preventDefault()
dragging = true
scene.value.setPointerCapture(ev.pointerId)
lastPointer = { x: ev.clientX, y: ev.clientY, time: performance.now() }
velocity = { x: 0, y: 0 }
if (raf) { cancelAnimationFrame(raf); raf = null }
}
// pointermove = on bouge la souris ou le doigt
const move = (ev) => {
if (!dragging) return
ev.preventDefault()
const now = performance.now()
const dx = ev.clientX - lastPointer.x
const dy = ev.clientY - lastPointer.y
// mise à jour des angles
angleY.value += dx * sensitivity
angleX.value -= dy * sensitivity
angleX.value = Math.max(-80, Math.min(80, angleX.value))
// calcul vitesse pour inertie
const dt = Math.max(1, now - lastPointer.time)
velocity.x = (dx / dt) * 16 * sensitivity
velocity.y = (-dy / dt) * 16 * sensitivity
lastPointer = { x: ev.clientX, y: ev.clientY, time: now }
applyTransform()
}
// pointerup = fin du drag
const end = (ev) => {
if (!dragging) return
dragging = false
try { scene.value.releasePointerCapture(ev.pointerId) } catch { }
if (enableInertia && (Math.abs(velocity.x) > minVelocity || Math.abs(velocity.y) > minVelocity)) {
if (!raf) raf = requestAnimationFrame(tickInertia)
}
}
// attach des events
scene.value.addEventListener('pointerdown', down)
scene.value.addEventListener('pointermove', move)
scene.value.addEventListener('pointerup', end)
scene.value.addEventListener('pointercancel', end)
scene.value.addEventListener('pointerleave', end)
// cleanup au démontage
onBeforeUnmount(() => {
cancelAnimationFrame(raf)
})
})
watch(() => props.position, () => {
applyRotation()
}, { deep: true })
watch(() => props.compilation, () => {
applyColor()
}, { deep: true })
watch(() => props.size, () => {
applySize()
})
</script>
<style>
html,
body {
height: 100%;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
font-family: system-ui, Segoe UI, Roboto, Helvetica, Arial;
-webkit-font-smoothing: antialiased;
}
/* scène avec perspective */
.scene {
height: var(--height);
width: var(--width);
perspective: 1000px;
touch-action: none;
/* essentiel pour empêcher le scroll pendant le drag */
/* height: 20px; */
transition: all .5s;
}
/* l'objet 3D (box simple) */
.box {
width: var(--width);
height: var(--height);
position: relative;
transform-style: preserve-3d;
transition: transform 120ms linear;
transition: height 120ms linear;
/* légère smoothing quand on lâche */
margin: auto;
user-select: none;
cursor: grab;
}
.box:active {
cursor: grabbing;
}
/* faces du box */
.face {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: white;
font-weight: 600;
backface-visibility: hidden;
/* border: 2px solid rgba(255, 255, 255, 0.06); */
box-sizing: border-box;
transform-origin: top right;
}
.front,
.back {
width: 100%;
height: 100%;
}
.face.top,
.face.bottom {
width: var(--width);
height: var(--depth);
}
.face.left,
.face.right {
width: var(--depth);
height: var(--height);
}
.face.front {
transform: translateX(0px) translateY(0px) translateZ(0px);
}
.face.back {
transform: rotateY(180deg) translateX(var(--width)) translateY(0px) translateZ(var(--depth));
}
.face.right {
transform: rotateY(90deg) translateX(0px) translateY(0px) translateZ(var(--width));
transform-origin: top left;
}
.face.left {
transform: rotateY(-90deg) translateX(0) translateY(0px) translateZ(var(--depth));
}
.face.top {
transform: rotateX(90deg) translateX(0px) translateY(calc(var(--depth) * -1)) translateZ(0px);
}
.face.top>* {
@apply rotate-180;
}
.face.bottom {
transform: rotateX(-90deg) translateX(0) translateY(0px) translateZ(calc(var(--height)));
}
.cover {
height: 100%;
width: 100%;
object-fit: cover;
}
.logo {
filter: drop-shadow(2px 2px 0 rgb(0 0 0 / 0.8));
}
</style>

View File

@@ -0,0 +1,296 @@
<template>
<article class="box box-scene z-10" ref="scene">
<div class="box-object" ref="box">
<div class="face front relative" ref="frontFace">
<img class="cover absolute" :src="`/${compilation.id}/cover.jpg`" alt="">
</div>
<div class="face back" ref="backFace">
{{ compilation.description }}
</div>
<div class="face right" ref="rightFace" />
<div class="face left" ref="leftFace" />
<div class="face top" ref="topFace">
<img class="logo h-full p-1" src="/logo.svg" alt="">
<img class="absolute block h-1/2" style="left:5%;" :src="`/${compilation.id}/title.svg`" alt="">
</div>
<div class="face bottom" ref="bottomFace" />
</div>
<OrganismCompilationPage :compilation="compilation" class="box-page" v-if="props.BoxState === 'selected'" />
</article>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
import type { Compilation, BoxState } from '~~/types/types'
const props = withDefaults(
defineProps<{
compilation: Compilation
BoxState?: BoxState
}>(),
{ BoxState: 'list' }
)
// --- Réfs ---
const scene = ref<HTMLElement>()
const box = ref<HTMLElement>()
const frontFace = ref<HTMLElement>()
const backFace = ref<HTMLElement>()
const rightFace = ref<HTMLElement>()
const leftFace = ref<HTMLElement>()
const topFace = ref<HTMLElement>()
const bottomFace = ref<HTMLElement>()
// --- Angles ---
const rotateX = ref(0)
const rotateY = ref(0)
const rotateZ = ref(0)
// --- Drag + inertie ---
let dragging = false
let lastPointer = { x: 0, y: 0, time: 0 }
let velocity = { x: 0, y: 0 }
let raf: number | null = null
const sensitivity = 0.3
const friction = 0.95
const minVelocity = 0.02
const enableInertia = true
// --- Transformations ---
function applyTransform(duration = 0.5) {
if (!box.value) return
rotateX.value = Math.round(rotateX.value)
rotateY.value = Math.round(rotateY.value)
rotateZ.value = Math.round(rotateZ.value)
box.value.style.transition = `transform ${duration}s ease`
box.value.style.transform = `rotateX(${rotateX.value}deg) rotateY(${rotateY.value}deg) rotateZ(${rotateZ.value}deg)`
}
// --- Gestion BoxState ---
function applyBoxState() {
switch (props.BoxState) {
case 'list':
rotateX.value = 76
rotateY.value = 0
rotateZ.value = 150
break
case 'selected':
rotateX.value = -20
rotateY.value = 20
rotateZ.value = 0
break
case 'hide':
rotateX.value = 76
rotateY.value = 0
rotateZ.value = 150
break
}
applyTransform(0.8) // transition fluide
}
// --- Couleurs ---
function applyColor() {
if (!frontFace.value || !backFace.value || !leftFace.value || !topFace.value || !bottomFace.value) return
frontFace.value.style.background = props.compilation.color2
backFace.value.style.background = `linear-gradient(to top, ${props.compilation.color1}, ${props.compilation.color2})`
leftFace.value.style.background = `linear-gradient(to top, ${props.compilation.color1}, ${props.compilation.color2})`
rightFace.value.style.background = `linear-gradient(to top, ${props.compilation.color1}, ${props.compilation.color2})`
topFace.value.style.background = `linear-gradient(to top, ${props.compilation.color2}, ${props.compilation.color2})`
bottomFace.value.style.background = props.compilation.color1
}
// --- Inertie ---
function tickInertia() {
if (!enableInertia) return
velocity.x *= friction
velocity.y *= friction
rotateX.value += velocity.y
rotateY.value += velocity.x
rotateX.value = Math.max(-80, Math.min(80, rotateX.value))
applyTransform(0.05) // court duration pour inertie fluide
if (Math.abs(velocity.x) > minVelocity || Math.abs(velocity.y) > minVelocity) {
raf = requestAnimationFrame(tickInertia)
} else {
raf = null
}
}
// --- Pointer events ---
onMounted(() => {
applyColor()
applyBoxState()
const down = (ev: PointerEvent) => {
ev.preventDefault()
dragging = true
scene.value?.setPointerCapture(ev.pointerId)
lastPointer = { x: ev.clientX, y: ev.clientY, time: performance.now() }
velocity = { x: 0, y: 0 }
if (raf) { cancelAnimationFrame(raf); raf = null }
}
const move = (ev: PointerEvent) => {
if (!dragging) return
ev.preventDefault()
const now = performance.now()
const dx = ev.clientX - lastPointer.x
const dy = ev.clientY - lastPointer.y
const dt = Math.max(1, now - lastPointer.time)
rotateY.value += dx * sensitivity
rotateX.value -= dy * sensitivity
rotateX.value = Math.max(-80, Math.min(80, rotateX.value))
velocity.x = (dx / dt) * 16 * sensitivity
velocity.y = (-dy / dt) * 16 * sensitivity
lastPointer = { x: ev.clientX, y: ev.clientY, time: now }
applyTransform(0) // immédiat pendant drag
}
const end = (ev: PointerEvent) => {
if (!dragging) return
dragging = false
try { scene.value?.releasePointerCapture(ev.pointerId) } catch { }
if (enableInertia && (Math.abs(velocity.x) > minVelocity || Math.abs(velocity.y) > minVelocity)) {
if (!raf) raf = requestAnimationFrame(tickInertia)
}
}
scene.value?.addEventListener('pointerdown', down)
scene.value?.addEventListener('pointermove', move)
scene.value?.addEventListener('pointerup', end)
scene.value?.addEventListener('pointercancel', end)
scene.value?.addEventListener('pointerleave', end)
onBeforeUnmount(() => {
cancelAnimationFrame(raf!)
})
})
// --- Watchers ---
watch(() => props.BoxState, () => applyBoxState())
watch(() => props.compilation, () => applyColor(), { deep: true })
</script>
<style lang="scss" scoped>
.box {
--size: 6px;
--height: calc(var(--size) * (100 / 3));
--width: calc(var(--size) * 50);
--depth: calc(var(--size) * 10);
transition: all .5s;
&.hide {
height: 0;
opacity: 0;
z-index: 0;
}
&.list {
@apply hover:scale-105;
}
&-scene {
height: calc(var(--size) * 20);
width: var(--width);
perspective: 1000px;
touch-action: none;
}
&-object {
width: var(--width);
height: var(--height);
position: relative;
transform-style: preserve-3d;
margin: auto;
user-select: none;
.list & {
cursor: pointer;
}
.selected & {
cursor: grab;
}
&:active {
cursor: grabbing;
}
}
.face {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: white;
font-weight: 600;
backface-visibility: hidden;
box-sizing: border-box;
border: 1px solid black;
}
.front,
.back {
width: 100%;
height: 100%;
}
.face.top,
.face.bottom {
width: var(--width);
height: var(--depth);
}
.face.left,
.face.right {
width: var(--depth);
height: var(--height);
}
.face.front {
transform: translateX(0) translateY(0) translateZ(var(--depth));
}
.face.back {
transform: rotateY(180deg) translateX(0) translateY(0) translateZ(0);
}
.face.right {
transform: rotateY(90deg) translateX(calc(var(--depth)*-1)) translateY(0px) translateZ(var(--width));
transform-origin: top left;
}
.face.left {
transform: rotateY(-90deg) translateX(calc(var(--depth)/2)) translateY(0) translateZ(calc(var(--depth)/2));
}
.face.top {
transform: rotateX(90deg) translateX(0px) translateY(calc(var(--depth)/2)) translateZ(calc(var(--depth)/2));
}
.face.top>* {
@apply rotate-180;
}
.face.bottom {
transform: rotateX(-90deg) translateX(0px) translateY(calc(var(--depth)* -0.5)) translateZ(calc(var(--height) - var(--depth)/2));
}
.cover {
width: 100%;
height: 100%;
object-fit: cover;
}
}
</style>

View File

@@ -0,0 +1,25 @@
<template>
<article
class="backdrop-blur-sm -mt-12 z-10 card w-56 h-80 p-3 bg-opacity-10 bg-white rounded-2xl shadow-lg flex flex-col overflow-hidden">
<!-- Cover -->
<figure class="flex-1 overflow-hidden rounded-t-xl">
<img :src="coverUrl" alt="Pochette de l'album" class="w-full h-full object-cover object-center" />
</figure>
<!-- Body -->
<div class="p-3 text-center bg-white rounded-b-xl">
<h2 class="text-base text-neutral-800 font-bold truncate">{{ props.track.title }}</h2>
<p class="text-sm text-neutral-500 truncate">
{{ props.track.artist.name }}
</p>
</div>
</article>
</template>
<script setup lang="ts">
import type { Track } from '~~/types/types'
const props = defineProps<{ track: Track }>()
const coverUrl = `https://f4.bcbits.com/img/${props.track.artist.coverId}_4.jpg`
</script>

View File

@@ -0,0 +1,53 @@
<template>
<div class="flex flex-col-reverse mt-16">
<molecule-box v-for="compilation in dataStore.getAllCompilations.slice().reverse()" :key="compilation.id"
:compilation="compilation" :BoxState="boxStates[compilation.id]" @click="() => openCompilation(compilation.id)"
:class="boxStates[compilation.id]" />
</div>
</template>
<script setup lang="ts">
import { useDataStore } from '~/store/data'
import type { BoxState } from '~~/types/types'
import { useRouter } from 'vue-router'
const dataStore = useDataStore()
const router = useRouter()
const boxStates = ref<Record<string, BoxState>>({})
function openCompilation(id: string) {
if (boxStates.value[id] === 'list') {
for (const key in boxStates.value) {
boxStates.value[key] = (key === id) ? 'selected' : 'hide'
}
window.history.pushState({}, '', '/compilation/' + id)
}
}
function closeCompilation(e: KeyboardEvent) {
if (e.key === 'Escape') {
for (const key in boxStates.value) {
boxStates.value[key] = 'list'
}
}
window.history.pushState({}, '', '/')
}
onMounted(async () => {
const dataStore = await useDataStore()
await dataStore.loadData()
dataStore.getAllCompilations.forEach(c => {
if (!(c.id in boxStates.value)) boxStates.value[c.id] = 'hide'
})
window.addEventListener('keydown', closeCompilation)
setTimeout(() => {
dataStore.getAllCompilations.forEach(c => {
boxStates.value[c.id] = 'list'
})
}, 333)
})
</script>

View File

@@ -0,0 +1,17 @@
<template>
<div class="mt-8 p-8 w-96">
<MoleculeCard v-for="track in dataStore.getTracksByCompilationId(compilation.id)" :key="track.id" :track="track" />
</div>
</template>
<script setup lang="ts">
import { useDataStore } from '~/store/data'
import type { Compilation } from '~~/types/types'
const props = defineProps<{
compilation: Compilation
}>()
const dataStore = useDataStore()
</script>

View File

@@ -1,17 +0,0 @@
<template>
<div class="flex flex-wrap justify-center">
<div class="bg-page-dark-bg text-white">
<div class="flex flex-col-reverse bg-gradient-to-r from-primary to-primary-dark">
<div class="mt-8 flex flex-wrap justify-center"
v-for="compilation in store.getAllCompilations.slice().reverse()">
<box :compilation="compilation" template="full" />
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useDataStore } from '@/store/dataStore'
const store = useDataStore()
</script>

View File

@@ -1,9 +1,20 @@
<template>
<div>
<h1>
<img class="logo h-full p-1" src="/logo.svg" alt="">
Compilations
indépendantes
</h1>
<div class="w-full flex flex-col items-center">
<header class="py-4">
<img class="logo p-1 w-80" src="/logo.svg" alt="">
<h1 class="dark:text-white text-center">
compilations
indépendantes
</h1>
</header>
<main>
<OrganismCompilationList />
</main>
</div>
</template>
<style>
.logo {
filter: drop-shadow(2px 2px 0 rgb(0 0 0 / 0.8));
}
</style>

View File

@@ -3,11 +3,11 @@
<div class="bg-page-dark-bg text-white">
<div class="flex flex-col-reverse bg-gradient-to-r from-primary to-primary-dark">
<div class="mt-8 flex flex-wrap justify-center">
<box :compilation="compilation" :position="currentPosition" :size="size" />
<molecule-box :compilation="compilation" :position="currentPosition" :size="size" />
<div class="devtool absolute right-4 text-white bg-black rounded-2xl px-4 py-2">
<button @click="currentPosition = poser">poser</button>
<button @click="currentPosition = face">face</button>
<button @click="currentPosition = dos">dos</button>
<button @click="currentPosition = boxPositions.side">side</button>
<button @click="currentPosition = boxPositions.front">front</button>
<button @click="currentPosition = boxPositions.back">back</button>
<div class="w-full block">
<input class="w-1/2" type="color" name="color1" id="color1" v-model="compilation.color1">
<input class="w-1/2" type="color" name="color1" id="color1" v-model="compilation.color2">
@@ -37,14 +37,16 @@
</div>
</div>
</div>
<molecule-card :track="track" />
</div>
</div>
</template>
<script setup lang="ts">
import type { BoxPosition } from '~~/types/types'
import type { BoxPosition, Compilation, Track } from '~~/types/types'
import { boxPositions } from '~/store/position'
const compilation = ref({
const compilation = ref<Compilation>({
id: 'ES00A',
name: 'zero',
duration: 2794,
@@ -54,13 +56,24 @@ const compilation = ref({
color3: '#00ff00',
})
const poser = { x: 76, y: 0, z: 150 }
const face = { x: -20, y: 20, z: 0 }
const dos = { x: -20, y: 200, z: 0 }
const track = ref<Track>({
id: 1,
compilationId: 'ES00A',
title: 'The grinding wheel',
artist: {
id: 0,
name: 'L\'Efondras',
url: '',
coverId: '0024705317',
},
start: 0,
url: 'https://arakirecords.bandcamp.com/track/the-grinding-wheel',
coverId: 'a3236746052',
})
const size = ref(6)
const currentPosition: Ref<BoxPosition> = ref(poser)
const currentPosition: Ref<BoxPosition> = ref(boxPositions.side)
//from-slate-800 to-zinc-900
</script>

View File

@@ -13,17 +13,24 @@ export const useDataStore = defineStore('data', {
actions: {
async loadData() {
if (this.isLoaded) return // Avoid re-fetching if already loaded
if (this.isLoaded) return
// Fetch your data once (e.g., from an API or local JSON)
const { data: compilations } = await useFetch('/api/compilations')
const { data: artists } = await useFetch('/api/artists')
const { data: tracks } = await useFetch('/api/tracks')
const { data: compilations } = await useFetch<Compilation[]>('/api/compilations')
const { data: artists } = await useFetch<Artist[]>('/api/artists')
const { data: rawTracks } = await useFetch<{ id: number, compilationId: string, title: string, artist: number, start: number, url: string, coverId: string }[]>('/api/tracks')
// Stocker les données de base
this.compilations = compilations.value ?? []
this.artists = artists.value ?? []
// Mapper les tracks pour remplacer l'artistId par l'objet Artist
const artistMap = new Map(this.artists.map(a => [a.id, a]))
this.tracks = (rawTracks.value ?? []).map(track => ({
...track,
artist: artistMap.get(track.artist) ?? { id: track.artist, name: 'Unknown', url: '', coverId: '' }
}))
// Set the data in the store
this.compilations = compilations.value
this.artists = artists.value
this.tracks = tracks.value
this.isLoaded = true
}
},
@@ -44,8 +51,8 @@ export const useDataStore = defineStore('data', {
getArtistById: (state) => (id: number) => state.artists.find(artist => artist.id === id),
// Obtenir toutes les pistes d'un artiste donné
getTracksByArtistId: (state) => (artistId: string) => {
return state.tracks.filter(track => track.artistId === artistId)
getTracksByArtistId: (state) => (artistId: number) => {
return state.tracks.filter(track => track.artist.id === artistId)
},
},
})

View File

@@ -1,6 +0,0 @@
// utils/cssVars.js
export function updateCssVar(name, value, el) {
// if (!import.meta.client) return;
const target = el?.$el || el || document.documentElement;
target.style.setProperty(name, value);
}