Feat: navigation clavier + routing ancre sur Play
- ArrowRight/Left : navigue entre les jeux aux limites (premier/dernier), passe à la slide prev/next - Ancres URL (#slug) : set à chaque changement de jeu, restaurées au chargement, effacées quand on quitte la slide Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
90f155b679
commit
fdab621b48
1 changed files with 61 additions and 16 deletions
|
|
@ -1,34 +1,57 @@
|
|||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
import { slides } from '@state/slides.svelte'
|
||||
import { slideTo } from '@router'
|
||||
import ResponsivePicture from '@components/ui/ResponsivePicture.svelte'
|
||||
|
||||
let { data } = $props()
|
||||
|
||||
// --- Derived ---
|
||||
const isActive = $derived(slides.active?.id === 'jouer')
|
||||
const games = $derived(data?.games ?? [])
|
||||
const isActive = $derived(slides.active?.id === 'jouer')
|
||||
const games = $derived(data?.games ?? [])
|
||||
const prevSlidePath = $derived(slides.all[slides.activeIndex - 1]?.path ?? null)
|
||||
const nextSlidePath = $derived(slides.all[slides.activeIndex + 1]?.path ?? null)
|
||||
|
||||
// --- State ---
|
||||
let currentIndex = $state(0) // index actif (carousel + bg)
|
||||
let displayedIndex = $state(0) // index du contenu affiché
|
||||
let currentIndex = $state(0)
|
||||
let displayedIndex = $state(0)
|
||||
let phase = $state('idle') // 'idle' | 'exiting' | 'entering'
|
||||
let slideDir = $state(1) // 1 = click droite (exit gauche), -1 = click gauche (exit droite)
|
||||
let slideDir = $state(1) // 1 = exit gauche, -1 = exit droite
|
||||
|
||||
// Bg crossfade
|
||||
let prevBgColor = $state(null)
|
||||
let bgFading = $state(false)
|
||||
let prevBgColor = $state(null)
|
||||
let bgFading = $state(false)
|
||||
|
||||
const displayedGame = $derived(games[displayedIndex] ?? null)
|
||||
|
||||
// Capture du hash synchrone avant que tout effect puisse le modifier
|
||||
const initialHash = window.location.hash.slice(1)
|
||||
|
||||
let t1 = null
|
||||
let t2 = null
|
||||
let t3 = null
|
||||
|
||||
// --- Ancres ---
|
||||
function setAnchor(index) {
|
||||
const slug = games[index]?.slug
|
||||
if (!slug) return
|
||||
history.replaceState(null, '', '#' + slug)
|
||||
}
|
||||
|
||||
function clearAnchor() {
|
||||
history.replaceState(null, '', window.location.pathname + window.location.search)
|
||||
}
|
||||
|
||||
// Restauration depuis l'ancre URL — une seule fois quand games est prêt
|
||||
$effect(() => {
|
||||
if (games.length === 0 || !initialHash) return
|
||||
const idx = games.findIndex(g => g.slug === initialHash)
|
||||
if (idx > 0) currentIndex = idx
|
||||
})
|
||||
|
||||
function selectGame(i) {
|
||||
if (i === currentIndex || phase !== 'idle' || !games.length) return
|
||||
|
||||
// slideDir = 1 → clic à droite → content sort par la gauche, entre par la droite
|
||||
slideDir = i > currentIndex ? 1 : -1
|
||||
phase = 'exiting'
|
||||
|
||||
|
|
@ -36,22 +59,39 @@
|
|||
clearTimeout(t2)
|
||||
clearTimeout(t3)
|
||||
|
||||
// Phase 2 (300ms) : swap contenu + carousel + bg simultanés
|
||||
t1 = setTimeout(() => {
|
||||
prevBgColor = displayedGame?.backgroundColor ?? null
|
||||
displayedIndex = i
|
||||
currentIndex = i
|
||||
phase = 'entering'
|
||||
bgFading = true
|
||||
setAnchor(i)
|
||||
|
||||
// Fin du bg fade
|
||||
t3 = setTimeout(() => { bgFading = false }, 500)
|
||||
|
||||
// Fin de l'entrée
|
||||
t2 = setTimeout(() => { phase = 'idle' }, 350)
|
||||
}, 300)
|
||||
}
|
||||
|
||||
// --- Clavier ---
|
||||
function onKeyDown(e) {
|
||||
if (!isActive) return
|
||||
if (e.key === 'ArrowRight') {
|
||||
e.preventDefault()
|
||||
if (currentIndex < games.length - 1) {
|
||||
selectGame(currentIndex + 1)
|
||||
} else if (nextSlidePath) {
|
||||
slideTo(nextSlidePath)
|
||||
}
|
||||
} else if (e.key === 'ArrowLeft') {
|
||||
e.preventDefault()
|
||||
if (currentIndex > 0) {
|
||||
selectGame(currentIndex - 1)
|
||||
} else if (prevSlidePath) {
|
||||
slideTo(prevSlidePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Réinitialisation quand on quitte la slide
|
||||
$effect(() => {
|
||||
if (!isActive) {
|
||||
|
|
@ -62,13 +102,18 @@
|
|||
displayedIndex = 0
|
||||
phase = 'idle'
|
||||
bgFading = false
|
||||
clearAnchor()
|
||||
}
|
||||
})
|
||||
|
||||
onMount(() => () => {
|
||||
clearTimeout(t1)
|
||||
clearTimeout(t2)
|
||||
clearTimeout(t3)
|
||||
onMount(() => {
|
||||
window.addEventListener('keydown', onKeyDown)
|
||||
return () => {
|
||||
window.removeEventListener('keydown', onKeyDown)
|
||||
clearTimeout(t1)
|
||||
clearTimeout(t2)
|
||||
clearTimeout(t3)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue