Silhouette animée de joueur de basket derrière les stats du hero
Ajoute une illustration originale (SVG, pas une photo) d'un joueur en plein dribble, en aplat noir/blanc avec liseré rouge de la marque, animée derrière la ligne de statistiques du hero (rebond du ballon, léger balancement du corps, traits de vitesse). Silhouette générique — aucune personne réelle identifiable, donc aucune question de droit à l'image. - Nouveau composant HeroPlayerSilhouette.jsx : figure construite à partir de segments à traits épais et jointures rondes (même logique que les pictogrammes sportifs plats type NBA/Jordan), plus fiable à main levée qu'un tracé bezier continu. - Positionnement en arrière-plan avec un masque en dégradé qui estompe la silhouette exactement derrière le texte des statistiques (déjà proche du seuil de contraste WCAG) et ne la laisse apparaître que dans l'espace vide à droite — le contraste du texte reste inchangé. - prefers-reduced-motion : figure et ballon figés, traits de vitesse masqués (première utilisation de cette media query dans le projet). aria-hidden + pointer-events:none (purement décoratif). - Réduite à 960px, masquée sous 640px (pas de place pour coexister avec la ligne de stats sur petit écran). - Aucun changement serveur, CSP, Docker ni nouvelle dépendance — CSS/SVG pur, dans l'esprit de l'animation marquee déjà existante. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fkg15RCxNgUys4ru73He2o
This commit is contained in:
3 files changed
+121
-1
No files matched your search
@@ -0,0 +1,60 @@
|
||||
// Purely decorative basketball-player silhouette animated behind the hero
|
||||
// stats row. An original flat-illustration silhouette (not a photo/video),
|
||||
// built from rounded-capsule limb segments — the same construction as
|
||||
// classic flat sports pictograms (NBA/Jordan-style marks) — in a duotone
|
||||
// matching the site theme. Avoids any droit-à-l'image concern since it
|
||||
// does not depict a real identifiable person.
|
||||
export default function HeroPlayerSilhouette({ className = '' }) {
|
||||
return (
|
||||
<svg
|
||||
className={`hero-stats-silhouette ${className}`.trim()}
|
||||
viewBox="0 0 360 300"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
role="presentation"
|
||||
focusable="false"
|
||||
>
|
||||
<g className="hspl-figure" fill="var(--ink)">
|
||||
<g fill="none" stroke="var(--ink)" strokeLinecap="round">
|
||||
{/* trailing arm, swung back for balance */}
|
||||
<line x1="222" y1="88" x2="192" y2="64" strokeWidth="15" />
|
||||
<line x1="192" y1="64" x2="164" y2="50" strokeWidth="10" />
|
||||
{/* dribbling arm, reaching down to the ball */}
|
||||
<line x1="230" y1="96" x2="262" y2="136" strokeWidth="16" />
|
||||
<line x1="262" y1="136" x2="290" y2="186" strokeWidth="10" />
|
||||
{/* torso */}
|
||||
<line x1="240" y1="80" x2="215" y2="130" strokeWidth="34" />
|
||||
<line x1="215" y1="130" x2="198" y2="168" strokeWidth="24" />
|
||||
{/* planted / front leg */}
|
||||
<line x1="205" y1="168" x2="224" y2="220" strokeWidth="22" />
|
||||
<line x1="224" y1="220" x2="218" y2="272" strokeWidth="14" />
|
||||
<line x1="218" y1="272" x2="240" y2="280" strokeWidth="12" />
|
||||
{/* trailing leg, extended back */}
|
||||
<line x1="196" y1="170" x2="150" y2="196" strokeWidth="22" />
|
||||
<line x1="150" y1="196" x2="108" y2="204" strokeWidth="13" />
|
||||
<line x1="108" y1="204" x2="84" y2="212" strokeWidth="11" />
|
||||
</g>
|
||||
{/* joint fillers smoothing the width change between segments */}
|
||||
<circle cx="192" cy="64" r="7.5" />
|
||||
<circle cx="262" cy="136" r="8" />
|
||||
<circle cx="215" cy="130" r="17" />
|
||||
<circle cx="201" cy="169" r="13" />
|
||||
<circle cx="224" cy="220" r="11" />
|
||||
<circle cx="150" cy="196" r="11" />
|
||||
<circle cx="248" cy="54" r="23" />
|
||||
</g>
|
||||
|
||||
<g className="hspl-ball" fill="var(--ink)">
|
||||
<circle cx="300" cy="208" r="22" />
|
||||
<path d="M280,196 C293,203 307,203 320,196" fill="none" stroke="var(--bg)" strokeWidth="2" />
|
||||
<path d="M300,187 C293,197 293,220 300,231" fill="none" stroke="var(--bg)" strokeWidth="2" />
|
||||
</g>
|
||||
|
||||
<g className="hspl-speedlines" stroke="var(--ink)" strokeWidth="3" strokeLinecap="round" fill="none">
|
||||
<path className="hspl-speedline" d="M42,196 L78,201" />
|
||||
<path className="hspl-speedline" d="M34,214 L72,214" />
|
||||
<path className="hspl-speedline" d="M40,232 L76,226" />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import Modal from '../components/Modal.jsx';
|
||||
import PastEventCard from '../components/PastEventCard.jsx';
|
||||
import { AdhesionForm, AdhesionModal } from '../components/AdhesionForm.jsx';
|
||||
import { associationSettings } from '../lib/siteSettings.js';
|
||||
import HeroPlayerSilhouette from '../components/HeroPlayerSilhouette.jsx';
|
||||
|
||||
export function InscriptionModal({ rencontre, onClose, onDone }) {
|
||||
const { user, login } = useAuth();
|
||||
@@ -265,7 +266,9 @@ export default function Home() {
|
||||
<button className="btn btn-outline">Découvrir l'association</button>
|
||||
</Link>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 42, marginTop: 54, paddingTop: 34, borderTop: '1px solid var(--border)' }}>
|
||||
<div className="hero-stats-wrap" style={{ position: 'relative', marginTop: 54, paddingTop: 34, borderTop: '1px solid var(--border)' }}>
|
||||
<HeroPlayerSilhouette />
|
||||
<div style={{ display: 'flex', gap: 42, position: 'relative', zIndex: 1 }}>
|
||||
{[[String(members.length), 'entreprises membres'], [String(passees.length), 'rencontres organisées'], ['30 ans', 'de partenariat']].map(([n, l]) => (
|
||||
<div key={l}>
|
||||
<div className="serif" style={{ fontSize: 34, fontWeight: 600 }}>{n}</div>
|
||||
@@ -274,6 +277,7 @@ export default function Home() {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<div className={content.hero_photo ? '' : 'placeholder-pattern'} style={{ aspectRatio: '4/5', borderRadius: 4, overflow: 'hidden', boxShadow: '0 30px 60px -30px rgba(27,27,27,.4)', display: 'flex', alignItems: 'flex-end', padding: content.hero_photo ? 0 : 22 }}>
|
||||
{content.hero_photo ? (
|
||||
|
||||
@@ -43,6 +43,60 @@ input:focus, textarea:focus, select:focus { outline: none; border-color: var(--r
|
||||
.marquee-fade { width: 40px; }
|
||||
}
|
||||
|
||||
/* Hero stats background: an animated basketball-player silhouette behind
|
||||
the "entreprises membres / rencontres organisées / X ans" row. Original
|
||||
illustration (not a photo), decorative only — see .hspl-* below. */
|
||||
.hero-stats-wrap { overflow: visible; }
|
||||
.hero-stats-silhouette {
|
||||
position: absolute; right: -20px; bottom: -34px; width: 300px; height: auto;
|
||||
pointer-events: none;
|
||||
/* Fades the silhouette out over the stat text (left) and lets it show at
|
||||
full (still very low) opacity only over the clear whitespace (right),
|
||||
so it never degrades the existing text contrast. */
|
||||
mask-image: linear-gradient(100deg, transparent 0%, transparent 24%, black 48%, black 100%);
|
||||
-webkit-mask-image: linear-gradient(100deg, transparent 0%, transparent 24%, black 48%, black 100%);
|
||||
}
|
||||
.hspl-figure {
|
||||
opacity: .08; transform-origin: 50% 100%;
|
||||
animation: hspl-drift 7s ease-in-out infinite alternate, hspl-glow 5s ease-in-out infinite;
|
||||
}
|
||||
.hspl-ball {
|
||||
opacity: .14; transform-box: fill-box; transform-origin: 50% 100%;
|
||||
animation: hspl-ball-bounce 1.4s infinite;
|
||||
}
|
||||
.hspl-speedline {
|
||||
opacity: .3;
|
||||
animation: hspl-speedline 2.4s ease-out infinite;
|
||||
}
|
||||
.hspl-speedline:nth-child(2) { animation-delay: .3s; }
|
||||
.hspl-speedline:nth-child(3) { animation-delay: .6s; }
|
||||
|
||||
@keyframes hspl-drift {
|
||||
from { transform: translateY(0) rotate(0deg); }
|
||||
to { transform: translateY(-10px) rotate(-1.4deg); }
|
||||
}
|
||||
@keyframes hspl-glow {
|
||||
0%, 100% { filter: drop-shadow(2px 0 0 rgba(193, 39, 45, 0)); }
|
||||
50% { filter: drop-shadow(2px 0 0 rgba(193, 39, 45, .18)); }
|
||||
}
|
||||
@keyframes hspl-ball-bounce {
|
||||
0% { transform: translateY(0); animation-timing-function: cubic-bezier(.55, 0, 1, .45); }
|
||||
18% { transform: translateY(38px) scaleY(.82) scaleX(1.1); animation-timing-function: cubic-bezier(0, .55, .45, 1); }
|
||||
50% { transform: translateY(0); animation-timing-function: cubic-bezier(.55, 0, 1, .45); }
|
||||
68% { transform: translateY(38px) scaleY(.82) scaleX(1.1); animation-timing-function: cubic-bezier(0, .55, .45, 1); }
|
||||
100% { transform: translateY(0); }
|
||||
}
|
||||
@keyframes hspl-speedline {
|
||||
0% { opacity: 0; transform: translateX(6px); }
|
||||
30% { opacity: 1; transform: translateX(0); }
|
||||
70% { opacity: 1; transform: translateX(-4px); }
|
||||
100% { opacity: 0; transform: translateX(-10px); }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.hspl-figure, .hspl-ball { animation: none !important; }
|
||||
.hspl-speedline { display: none !important; }
|
||||
}
|
||||
|
||||
.container { max-width: 1180px; margin: 0 auto; padding-left: 32px; padding-right: 32px; }
|
||||
.serif { font-family: var(--serif); }
|
||||
|
||||
@@ -248,6 +302,7 @@ input:focus, textarea:focus, select:focus { outline: none; border-color: var(--r
|
||||
@media (max-width: 960px) {
|
||||
.grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr !important; }
|
||||
.hero-grid { grid-template-columns: 1fr !important; }
|
||||
.hero-stats-silhouette { width: 190px; right: -10px; bottom: -10px; }
|
||||
}
|
||||
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
|
||||
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }
|
||||
@@ -274,6 +329,7 @@ input:focus, textarea:focus, select:focus { outline: none; border-color: var(--r
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.container { padding-left: 20px; padding-right: 20px; }
|
||||
.hero-stats-silhouette { display: none; }
|
||||
.admin-nav { grid-template-columns: 1fr; gap: 20px; }
|
||||
.admin-sidebar-footer { align-items: stretch; flex-direction: column; }
|
||||
.admin-sidebar-action { width: 100%; }
|
||||
|
||||
Reference in new issue
Block a user