mirror of
https://github.com/R0m1k3/PleinR.git
synced 2026-10-24 07:38:04 +02:00
Accueil: carrousel 3 cartes avec rotation en fondu toutes les 20s
Remplace le defilement continu par 3 cartes fixes et propres. Toutes les 20 s, les cartes disparaissent une a une (fondu decale) puis les 3 suivantes apparaissent une a une. Rotation equitable sur tous les adherents actifs. Respecte prefers-reduced-motion (bascule sans fondu). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
64ab10dea1
commit
a4f03a21aa
2 files changed
+46
-48
No files matched your search
@@ -352,37 +352,3 @@ button {
|
|||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- Carrousel d'adhérents (défilement automatique) ---- */
|
|
||||||
@keyframes pleinr-marquee {
|
|
||||||
from {
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: translateX(-50%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.pleinr-marquee {
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
-webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
|
|
||||||
mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
|
|
||||||
}
|
|
||||||
.pleinr-marquee-track {
|
|
||||||
display: flex;
|
|
||||||
gap: 18px;
|
|
||||||
width: max-content;
|
|
||||||
animation-name: pleinr-marquee;
|
|
||||||
animation-timing-function: linear;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
}
|
|
||||||
.pleinr-marquee:hover .pleinr-marquee-track {
|
|
||||||
animation-play-state: paused;
|
|
||||||
}
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
|
||||||
.pleinr-marquee-track {
|
|
||||||
animation: none;
|
|
||||||
}
|
|
||||||
.pleinr-marquee {
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
export type CarouselMember = {
|
export type CarouselMember = {
|
||||||
@@ -17,15 +18,18 @@ export type CarouselMember = {
|
|||||||
const STRIPE_WARM =
|
const STRIPE_WARM =
|
||||||
"repeating-linear-gradient(45deg,#efe9da,#efe9da 12px,#e6ddc9 12px,#e6ddc9 24px)";
|
"repeating-linear-gradient(45deg,#efe9da,#efe9da 12px,#e6ddc9 12px,#e6ddc9 24px)";
|
||||||
|
|
||||||
function Card({ m }: { m: CarouselMember }) {
|
const VISIBLE = 3;
|
||||||
|
const INTERVAL_MS = 20_000;
|
||||||
|
const STAGGER_MS = 220; // décalage de disparition/apparition entre cartes
|
||||||
|
const FADE_MS = 450;
|
||||||
|
|
||||||
|
function Card({ m, visible, index }: { m: CarouselMember; visible: boolean; index: number }) {
|
||||||
const image = m.coverUrl || m.logoUrl;
|
const image = m.coverUrl || m.logoUrl;
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href={`/adherents/${m.id}`}
|
href={`/adherents/${m.id}`}
|
||||||
className="lift-card"
|
className="lift-card"
|
||||||
style={{
|
style={{
|
||||||
flex: "0 0 300px",
|
|
||||||
width: 300,
|
|
||||||
background: "#fff",
|
background: "#fff",
|
||||||
border: "1px solid #e6dcc6",
|
border: "1px solid #e6dcc6",
|
||||||
borderRadius: 18,
|
borderRadius: 18,
|
||||||
@@ -33,6 +37,10 @@ function Card({ m }: { m: CarouselMember }) {
|
|||||||
textDecoration: "none",
|
textDecoration: "none",
|
||||||
color: "inherit",
|
color: "inherit",
|
||||||
display: "block",
|
display: "block",
|
||||||
|
opacity: visible ? 1 : 0,
|
||||||
|
transform: visible ? "translateY(0)" : "translateY(14px)",
|
||||||
|
transition: `opacity ${FADE_MS}ms ease, transform ${FADE_MS}ms ease`,
|
||||||
|
transitionDelay: `${index * STAGGER_MS}ms`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -73,20 +81,44 @@ function Card({ m }: { m: CarouselMember }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function MembersCarousel({ members }: { members: CarouselMember[] }) {
|
export function MembersCarousel({ members }: { members: CarouselMember[] }) {
|
||||||
if (members.length === 0) return null;
|
const [start, setStart] = useState(0);
|
||||||
|
const [visible, setVisible] = useState(true);
|
||||||
|
|
||||||
// Vitesse proportionnelle au nombre de cartes (≈ 6 s par carte).
|
const total = members.length;
|
||||||
const duration = Math.max(20, members.length * 6);
|
const canRotate = total > VISIBLE;
|
||||||
// Doublé pour un défilement en boucle sans couture.
|
|
||||||
const loop = [...members, ...members];
|
useEffect(() => {
|
||||||
|
if (!canRotate) return;
|
||||||
|
// Respecte la préférence de réduction d'animation.
|
||||||
|
const reduce = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
||||||
|
|
||||||
|
const tick = setInterval(() => {
|
||||||
|
if (reduce) {
|
||||||
|
setStart((s) => (s + VISIBLE) % total);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Disparition une à une, puis changement, puis réapparition une à une.
|
||||||
|
setVisible(false);
|
||||||
|
const outMs = FADE_MS + STAGGER_MS * (VISIBLE - 1);
|
||||||
|
setTimeout(() => {
|
||||||
|
setStart((s) => (s + VISIBLE) % total);
|
||||||
|
setVisible(true);
|
||||||
|
}, outMs);
|
||||||
|
}, INTERVAL_MS);
|
||||||
|
|
||||||
|
return () => clearInterval(tick);
|
||||||
|
}, [canRotate, total]);
|
||||||
|
|
||||||
|
if (total === 0) return null;
|
||||||
|
|
||||||
|
// Fenêtre de 3 cartes, avec bouclage propre si on dépasse la fin.
|
||||||
|
const shown = Array.from({ length: Math.min(VISIBLE, total) }, (_, i) => members[(start + i) % total]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pleinr-marquee" aria-label="Adhérents à l'honneur">
|
<div className="grid grid-3" style={{ gap: 18 }}>
|
||||||
<div className="pleinr-marquee-track" style={{ animationDuration: `${duration}s` }}>
|
{shown.map((m, i) => (
|
||||||
{loop.map((m, i) => (
|
<Card key={i} m={m} visible={visible} index={i} />
|
||||||
<Card key={`${m.id}-${i}`} m={m} />
|
))}
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user