diff --git a/web/src/components/HeroPlayerSilhouette.jsx b/web/src/components/HeroPlayerSilhouette.jsx new file mode 100644 index 0000000..6ada6f6 --- /dev/null +++ b/web/src/components/HeroPlayerSilhouette.jsx @@ -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 ( + + ); +} diff --git a/web/src/pages/Home.jsx b/web/src/pages/Home.jsx index c9abb92..9ea7819 100644 --- a/web/src/pages/Home.jsx +++ b/web/src/pages/Home.jsx @@ -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,13 +266,16 @@ export default function Home() { -
- {[[String(members.length), 'entreprises membres'], [String(passees.length), 'rencontres organisées'], ['30 ans', 'de partenariat']].map(([n, l]) => ( -
-
{n}
-
{l}
-
- ))} +
+ +
+ {[[String(members.length), 'entreprises membres'], [String(passees.length), 'rencontres organisées'], ['30 ans', 'de partenariat']].map(([n, l]) => ( +
+
{n}
+
{l}
+
+ ))} +
diff --git a/web/src/styles.css b/web/src/styles.css index c7c3c9e..d02f9ca 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -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%; }