diff --git a/site/templates/play.json.php b/site/templates/play.json.php
index 5a92136..feeb766 100644
--- a/site/templates/play.json.php
+++ b/site/templates/play.json.php
@@ -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()
diff --git a/src/views/Play.svelte b/src/views/Play.svelte
index aabb533..3700179 100644
--- a/src/views/Play.svelte
+++ b/src/views/Play.svelte
@@ -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)
- currentIndex = 0
- displayedIndex = 0
- isOut = false
- isTransitioning = false
+ clearTimeout(t3)
+ currentIndex = 0
+ displayedIndex = 0
+ phase = 'idle'
+ bgFading = false
}
})
onMount(() => () => {
clearTimeout(t1)
clearTimeout(t2)
+ clearTimeout(t3)
})
@@ -67,9 +77,13 @@
style={displayedGame?.backgroundColor ? `--background-color: ${displayedGame.backgroundColor}` : ''}
>
-
-
-
+
+ {#if bgFading && prevBgColor}
+
+ {/if}
+
+
+
@@ -122,11 +136,24 @@
+
+ {#if displayedGame?.preview}
+
+

+
+ {/if}
-
+
@@ -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;
}
}