Feat: transitions Play soignées — exit/enter directionnel + bg crossfade + game-preview
All checks were successful
Deploy / Deploy to Production (push) Successful in 17s
All checks were successful
Deploy / Deploy to Production (push) Successful in 17s
- Phase exiting (300ms) : featured + game-preview glissent/fondent dans slideDir - Phase entering (350ms) : nouvel contenu entre depuis la direction opposée - Swap currentIndex à 300ms : carousel grossit + bg crossfade simultanés - Ajout game-preview (image preview côté droit, grid-area 3/13) - Ajout preview dans le template JSON PHP - Masqué sur mobile Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
bbab752fd6
commit
600ce937a3
2 changed files with 133 additions and 59 deletions
|
|
@ -9,6 +9,7 @@ $specificData = [
|
|||
'description' => $game->description()->value(),
|
||||
'thumbnail' => $game->thumbnail()->toFile()?->url(),
|
||||
'backgroundColor' => $game->backgroundColor()->value() ?: null,
|
||||
'preview' => $game->preview()->toFile()?->url(),
|
||||
'playLink' => $game->playLink()->value() ?: null,
|
||||
];
|
||||
})->values()
|
||||
|
|
|
|||
|
|
@ -9,38 +9,46 @@
|
|||
const games = $derived(data?.games ?? [])
|
||||
|
||||
// --- State ---
|
||||
let currentIndex = $state(0) // index de la sélection (mis à jour immédiatement)
|
||||
let displayedIndex = $state(0) // index affiché (mis à jour au milieu de la transition)
|
||||
let isOut = $state(false)
|
||||
let isTransitioning = $state(false)
|
||||
let slideDir = $state(1) // -1 : vers la droite, 1 : vers la gauche
|
||||
let currentIndex = $state(0) // index actif (carousel + bg)
|
||||
let displayedIndex = $state(0) // index du contenu affiché
|
||||
let phase = $state('idle') // 'idle' | 'exiting' | 'entering'
|
||||
let slideDir = $state(1) // 1 = click droite (exit gauche), -1 = click gauche (exit droite)
|
||||
|
||||
// Bg crossfade
|
||||
let prevBgColor = $state(null)
|
||||
let bgFading = $state(false)
|
||||
|
||||
const displayedGame = $derived(games[displayedIndex] ?? null)
|
||||
|
||||
let t1 = null
|
||||
let t2 = null
|
||||
let t3 = null
|
||||
|
||||
function selectGame(i) {
|
||||
if (i === currentIndex || isTransitioning || !games.length) return
|
||||
if (i === currentIndex || phase !== 'idle' || !games.length) return
|
||||
|
||||
slideDir = i > currentIndex ? -1 : 1
|
||||
currentIndex = i
|
||||
isOut = true
|
||||
isTransitioning = true
|
||||
// slideDir = 1 → clic à droite → content sort par la gauche, entre par la droite
|
||||
slideDir = i > currentIndex ? 1 : -1
|
||||
phase = 'exiting'
|
||||
|
||||
clearTimeout(t1)
|
||||
clearTimeout(t2)
|
||||
clearTimeout(t3)
|
||||
|
||||
// Milieu : swap du contenu affiché et du fond (éléments invisibles à ce moment)
|
||||
// Phase 2 (300ms) : swap contenu + carousel + bg simultanés
|
||||
t1 = setTimeout(() => {
|
||||
prevBgColor = displayedGame?.backgroundColor ?? null
|
||||
displayedIndex = i
|
||||
isOut = false
|
||||
}, 300)
|
||||
currentIndex = i
|
||||
phase = 'entering'
|
||||
bgFading = true
|
||||
|
||||
// Fin de transition
|
||||
t2 = setTimeout(() => {
|
||||
isTransitioning = false
|
||||
}, 600)
|
||||
// Fin du bg fade
|
||||
t3 = setTimeout(() => { bgFading = false }, 500)
|
||||
|
||||
// Fin de l'entrée
|
||||
t2 = setTimeout(() => { phase = 'idle' }, 350)
|
||||
}, 300)
|
||||
}
|
||||
|
||||
// Réinitialisation quand on quitte la slide
|
||||
|
|
@ -48,16 +56,18 @@
|
|||
if (!isActive) {
|
||||
clearTimeout(t1)
|
||||
clearTimeout(t2)
|
||||
clearTimeout(t3)
|
||||
currentIndex = 0
|
||||
displayedIndex = 0
|
||||
isOut = false
|
||||
isTransitioning = false
|
||||
phase = 'idle'
|
||||
bgFading = false
|
||||
}
|
||||
})
|
||||
|
||||
onMount(() => () => {
|
||||
clearTimeout(t1)
|
||||
clearTimeout(t2)
|
||||
clearTimeout(t3)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
@ -67,9 +77,13 @@
|
|||
style={displayedGame?.backgroundColor ? `--background-color: ${displayedGame.backgroundColor}` : ''}
|
||||
>
|
||||
|
||||
<!-- Fond : image + overlay, crossfade au changement de jeu -->
|
||||
<div class="play-bg" class:is-out={isOut} aria-hidden="true">
|
||||
</div>
|
||||
<!-- Ancien fond en fondu sortant (crossfade bg) -->
|
||||
{#if bgFading && prevBgColor}
|
||||
<div class="play-bg-fade" style="background: {prevBgColor}" aria-hidden="true"></div>
|
||||
{/if}
|
||||
|
||||
<!-- Fond SVG + overlay -->
|
||||
<div class="play-bg" aria-hidden="true"></div>
|
||||
|
||||
<!-- Lignes courbes décoratives (SVG) -->
|
||||
<div class="play-curves" aria-hidden="true">
|
||||
|
|
@ -122,11 +136,24 @@
|
|||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Preview image (côté droit, décoratif) -->
|
||||
{#if displayedGame?.preview}
|
||||
<div
|
||||
class="game-preview"
|
||||
class:exiting={phase === 'exiting'}
|
||||
class:entering={phase === 'entering'}
|
||||
style="--slide-dir: {slideDir}"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<img src={displayedGame.preview} alt="" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Section principale : lettering + description + boutons de jeu -->
|
||||
<!-- Section principale : lettering + description + bouton -->
|
||||
<div
|
||||
class="play-featured"
|
||||
class:is-out={isOut}
|
||||
class:exiting={phase === 'exiting'}
|
||||
class:entering={phase === 'entering'}
|
||||
style="--slide-dir: {slideDir}"
|
||||
aria-live="polite"
|
||||
>
|
||||
|
|
@ -185,7 +212,20 @@
|
|||
background: var(--background-color);
|
||||
}
|
||||
|
||||
/* --- Background --- */
|
||||
/* --- Bg crossfade --- */
|
||||
.play-bg-fade {
|
||||
grid-area: 1/1 / span 20 / span 20;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
animation: bg-fade-out 0.5s ease forwards;
|
||||
}
|
||||
|
||||
@keyframes bg-fade-out {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
|
||||
/* --- Background SVG --- */
|
||||
.play-bg {
|
||||
background-image: url(/static/media/background-play.f8db5aa42e2983197126.svg);
|
||||
background-position: 50%;
|
||||
|
|
@ -198,18 +238,6 @@
|
|||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.play-bg.is-out {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.play-bg-img {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* --- Lignes courbes SVG --- */
|
||||
|
|
@ -225,33 +253,57 @@
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
/* --- Animations partagées featured + preview --- */
|
||||
|
||||
/* slideDir = 1 (clic droite) → exit vers la gauche, entrée depuis la droite */
|
||||
/* slideDir = -1 (clic gauche) → exit vers la droite, entrée depuis la gauche */
|
||||
|
||||
@keyframes content-exit {
|
||||
from { opacity: 1; transform: translateX(0); }
|
||||
to { opacity: 0; transform: translateX(calc(var(--slide-dir) * -40px)); }
|
||||
}
|
||||
|
||||
@keyframes content-enter {
|
||||
from { opacity: 0; transform: translateX(calc(var(--slide-dir) * 40px)); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
|
||||
.play-featured.exiting,
|
||||
.game-preview.exiting {
|
||||
animation: content-exit 0.3s ease forwards;
|
||||
}
|
||||
|
||||
.play-featured.entering,
|
||||
.game-preview.entering {
|
||||
animation: content-enter 0.35s ease forwards;
|
||||
}
|
||||
|
||||
/* --- Section principale --- */
|
||||
.play-featured {
|
||||
grid-area: 4/4 / span 12 / span 10;
|
||||
grid-area: 2/4 / span 12 / span 10;
|
||||
z-index: var(--z-content);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 1.5rem;
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
|
||||
.play-featured.is-out {
|
||||
opacity: 0;
|
||||
transform: translateX(calc(var(--slide-dir) * 40px));
|
||||
}
|
||||
|
||||
.play-lettering {
|
||||
width: clamp(180px,18.77vw,362px);
|
||||
width: clamp(180px, 18.77vw, 362px);
|
||||
object-fit: contain;
|
||||
object-position: left center;
|
||||
}
|
||||
|
||||
.play-description {
|
||||
font-size: var(--font-size-paragraph);
|
||||
color: var(--color-text);
|
||||
line-height: 1.5;
|
||||
max-width: 42ch;
|
||||
font-family: Danzza, sans-serif;
|
||||
font-size: var(--font-size-subtitle);
|
||||
font-weight: 400;
|
||||
line-height: 1.6;
|
||||
margin: 0 0 20px;
|
||||
max-width: 600px;
|
||||
opacity: .9;
|
||||
text-shadow: 0 1px 15px rgba(0,0,0,.6);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.play-actions {
|
||||
|
|
@ -260,6 +312,23 @@
|
|||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* --- Preview image (droite) --- */
|
||||
.game-preview {
|
||||
grid-area: 3/13 / span 14 / span 7;
|
||||
z-index: var(--z-content);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.game-preview img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
object-position: center;
|
||||
}
|
||||
|
||||
/* --- Carrousel --- */
|
||||
.play-carousel {
|
||||
grid-area: 16/4 / span 3 / span 14;
|
||||
|
|
@ -278,19 +347,17 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: clamp(140px,15.09vw,291px);
|
||||
border-radius: 12px;
|
||||
border: 2px solid transparent;
|
||||
width: clamp(140px, 15.09vw, 291px);
|
||||
gap: 0.5rem;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
opacity: 0.5;
|
||||
transition: opacity 0.4s var(--ease-standard), transform 0.4s var(--ease-standard);
|
||||
transition: all 0.4s var(--ease-standard);
|
||||
}
|
||||
|
||||
.play-carousel-item.active button {
|
||||
opacity: 1;
|
||||
width: clamp(170px,18.41vw,355px);
|
||||
width: clamp(170px, 18.41vw, 355px);
|
||||
}
|
||||
|
||||
.play-carousel-item button img {
|
||||
|
|
@ -306,11 +373,15 @@
|
|||
|
||||
.play-carousel-item.active button img {
|
||||
border: 4px solid var(--color-primary);
|
||||
border-radius: 4.9375rem;
|
||||
border-radius: 25%;
|
||||
}
|
||||
|
||||
/* --- Mobile (≤ 700px) --- */
|
||||
@media screen and (max-width: 700px) {
|
||||
.game-preview {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.play-featured {
|
||||
grid-area: 3/2 / span 12 / span 18;
|
||||
}
|
||||
|
|
@ -323,8 +394,10 @@
|
|||
/* Reduced motion */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.play-featured,
|
||||
.play-bg,
|
||||
.game-preview,
|
||||
.play-bg-fade,
|
||||
.play-carousel-item button {
|
||||
animation: none;
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue