295 lines
6.9 KiB
Svelte
295 lines
6.9 KiB
Svelte
|
|
<script>
|
||
|
|
import { onMount } from 'svelte'
|
||
|
|
import { slides } from '@state/slides.svelte'
|
||
|
|
|
||
|
|
let { data } = $props()
|
||
|
|
|
||
|
|
// --- Derived ---
|
||
|
|
const isActive = $derived(slides.active?.id === 'jouer')
|
||
|
|
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
|
||
|
|
|
||
|
|
const displayedGame = $derived(games[displayedIndex] ?? null)
|
||
|
|
|
||
|
|
let t1 = null
|
||
|
|
let t2 = null
|
||
|
|
|
||
|
|
function selectGame(i) {
|
||
|
|
if (i === currentIndex || isTransitioning || !games.length) return
|
||
|
|
|
||
|
|
slideDir = i > currentIndex ? -1 : 1
|
||
|
|
currentIndex = i
|
||
|
|
isOut = true
|
||
|
|
isTransitioning = true
|
||
|
|
|
||
|
|
clearTimeout(t1)
|
||
|
|
clearTimeout(t2)
|
||
|
|
|
||
|
|
// Milieu : swap du contenu affiché et du fond (éléments invisibles à ce moment)
|
||
|
|
t1 = setTimeout(() => {
|
||
|
|
displayedIndex = i
|
||
|
|
isOut = false
|
||
|
|
}, 300)
|
||
|
|
|
||
|
|
// Fin de transition
|
||
|
|
t2 = setTimeout(() => {
|
||
|
|
isTransitioning = false
|
||
|
|
}, 600)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Réinitialisation quand on quitte la slide
|
||
|
|
$effect(() => {
|
||
|
|
if (!isActive) {
|
||
|
|
clearTimeout(t1)
|
||
|
|
clearTimeout(t2)
|
||
|
|
currentIndex = 0
|
||
|
|
displayedIndex = 0
|
||
|
|
isOut = false
|
||
|
|
isTransitioning = false
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
onMount(() => () => {
|
||
|
|
clearTimeout(t1)
|
||
|
|
clearTimeout(t2)
|
||
|
|
})
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<section class="play golden-grid slide" aria-label="Jouer">
|
||
|
|
|
||
|
|
<!-- Fond : image + overlay, crossfade au changement de jeu -->
|
||
|
|
<div class="play-bg" class:is-out={isOut} aria-hidden="true">
|
||
|
|
{#if displayedGame?.background_image}
|
||
|
|
<img class="play-bg-img" src={displayedGame.background_image} alt="" />
|
||
|
|
{/if}
|
||
|
|
<div class="play-bg-overlay"></div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Lignes verticales décoratives -->
|
||
|
|
<div class="vertical-line-start" aria-hidden="true"></div>
|
||
|
|
<div class="vertical-line vertical-line-col8" aria-hidden="true"></div>
|
||
|
|
<div class="vertical-line-center" aria-hidden="true"></div>
|
||
|
|
<div class="vertical-line vertical-line-col14" aria-hidden="true"></div>
|
||
|
|
<div class="vertical-line-end" aria-hidden="true"></div>
|
||
|
|
|
||
|
|
<!-- Section principale : lettering + description + boutons de jeu -->
|
||
|
|
<div
|
||
|
|
class="play-featured"
|
||
|
|
class:is-out={isOut}
|
||
|
|
style="--slide-dir: {slideDir}"
|
||
|
|
aria-live="polite"
|
||
|
|
>
|
||
|
|
{#if displayedGame}
|
||
|
|
{#if displayedGame.lettering}
|
||
|
|
<img
|
||
|
|
class="play-lettering"
|
||
|
|
src={displayedGame.lettering}
|
||
|
|
alt={displayedGame.title}
|
||
|
|
/>
|
||
|
|
{/if}
|
||
|
|
|
||
|
|
{#if displayedGame.description}
|
||
|
|
<div class="play-description">{@html displayedGame.description}</div>
|
||
|
|
{/if}
|
||
|
|
|
||
|
|
<div class="play-actions">
|
||
|
|
{#if displayedGame.play_links?.length}
|
||
|
|
{#each displayedGame.play_links as link}
|
||
|
|
<a
|
||
|
|
href={link.url}
|
||
|
|
target="_blank"
|
||
|
|
rel="noopener noreferrer"
|
||
|
|
class="button"
|
||
|
|
>{link.label}</a>
|
||
|
|
{/each}
|
||
|
|
{:else}
|
||
|
|
<button class="button" disabled>Coming soon</button>
|
||
|
|
{/if}
|
||
|
|
</div>
|
||
|
|
{/if}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Carrousel de sélection des jeux -->
|
||
|
|
<nav class="play-carousel" aria-label="Choisir un jeu">
|
||
|
|
<ul role="list">
|
||
|
|
{#each games as game, i}
|
||
|
|
<li class="play-carousel-item" class:active={i === currentIndex}>
|
||
|
|
<button
|
||
|
|
aria-label={game.title}
|
||
|
|
aria-current={i === currentIndex ? 'true' : undefined}
|
||
|
|
onclick={() => selectGame(i)}
|
||
|
|
>
|
||
|
|
{#if game.thumbnail}
|
||
|
|
<img src={game.thumbnail} alt="" />
|
||
|
|
{/if}
|
||
|
|
<span class="play-carousel-title">{game.title}</span>
|
||
|
|
</button>
|
||
|
|
</li>
|
||
|
|
{/each}
|
||
|
|
</ul>
|
||
|
|
</nav>
|
||
|
|
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<style>
|
||
|
|
.play {
|
||
|
|
background: #000;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* --- Background --- */
|
||
|
|
.play-bg {
|
||
|
|
grid-area: 1/1 / span 20 / span 20;
|
||
|
|
position: relative;
|
||
|
|
overflow: hidden;
|
||
|
|
transition: opacity 0.4s ease;
|
||
|
|
}
|
||
|
|
|
||
|
|
.play-bg.is-out {
|
||
|
|
opacity: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.play-bg-img {
|
||
|
|
position: absolute;
|
||
|
|
inset: 0;
|
||
|
|
width: 100%;
|
||
|
|
height: 100%;
|
||
|
|
object-fit: cover;
|
||
|
|
}
|
||
|
|
|
||
|
|
.play-bg-overlay {
|
||
|
|
position: absolute;
|
||
|
|
inset: 0;
|
||
|
|
background: linear-gradient(
|
||
|
|
to right,
|
||
|
|
rgba(0, 0, 0, 0.75) 0%,
|
||
|
|
rgba(0, 0, 0, 0.35) 60%,
|
||
|
|
rgba(0, 0, 0, 0.1) 100%
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* --- Lignes verticales --- */
|
||
|
|
.vertical-line-col8 {
|
||
|
|
grid-area: 1/8 / span 20 / span 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
.vertical-line-col14 {
|
||
|
|
grid-area: 1/14 / span 20 / span 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* --- Section principale --- */
|
||
|
|
.play-featured {
|
||
|
|
grid-area: 4/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 {
|
||
|
|
max-height: 120px;
|
||
|
|
width: auto;
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
|
||
|
|
.play-actions {
|
||
|
|
display: flex;
|
||
|
|
gap: 1rem;
|
||
|
|
flex-wrap: wrap;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* --- Carrousel --- */
|
||
|
|
.play-carousel {
|
||
|
|
grid-area: 16/4 / span 3 / span 14;
|
||
|
|
z-index: var(--z-content);
|
||
|
|
}
|
||
|
|
|
||
|
|
.play-carousel ul {
|
||
|
|
list-style: none;
|
||
|
|
display: flex;
|
||
|
|
gap: 1.5rem;
|
||
|
|
align-items: flex-end;
|
||
|
|
height: 100%;
|
||
|
|
}
|
||
|
|
|
||
|
|
.play-carousel-item button {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
align-items: center;
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
|
||
|
|
.play-carousel-item button img {
|
||
|
|
width: 64px;
|
||
|
|
height: 64px;
|
||
|
|
object-fit: cover;
|
||
|
|
border-radius: 12px;
|
||
|
|
border: 2px solid transparent;
|
||
|
|
transition: border-color 0.4s var(--ease-standard);
|
||
|
|
}
|
||
|
|
|
||
|
|
.play-carousel-title {
|
||
|
|
font-size: var(--font-size-caption);
|
||
|
|
color: var(--color-text);
|
||
|
|
text-align: center;
|
||
|
|
}
|
||
|
|
|
||
|
|
.play-carousel-item.active button {
|
||
|
|
opacity: 1;
|
||
|
|
transform: scale(1.15) translateY(-4px);
|
||
|
|
}
|
||
|
|
|
||
|
|
.play-carousel-item.active button img {
|
||
|
|
border-color: var(--color-primary);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* --- Mobile (≤ 700px) --- */
|
||
|
|
@media screen and (max-width: 700px) {
|
||
|
|
.play-bg-overlay {
|
||
|
|
background: rgba(0, 0, 0, 0.6);
|
||
|
|
}
|
||
|
|
|
||
|
|
.play-featured {
|
||
|
|
grid-area: 3/2 / span 12 / span 18;
|
||
|
|
}
|
||
|
|
|
||
|
|
.play-carousel {
|
||
|
|
grid-area: 16/2 / span 3 / span 18;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Reduced motion */
|
||
|
|
@media (prefers-reduced-motion: reduce) {
|
||
|
|
.play-featured,
|
||
|
|
.play-bg,
|
||
|
|
.play-carousel-item button {
|
||
|
|
transition: none;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</style>
|