feat(classiques): enforce uniform physical scroll speed across all marquees and match clock box design from img 1

This commit is contained in:
mathis committed 2026-08-31 00:02:39 +02:00
1 parent 75d0a05f21
commit 5d954af44a
5 files changed
+59 -38

No files matched your search

+29 -10
View File
@@ -1,23 +1,42 @@
import React, { useRef, useState, useEffect } from 'react'; import React, { useRef, useState, useEffect } from 'react';
import './MarqueeText.css'; import './MarqueeText.css';
const MarqueeText = ({ text, className, style, duration = '10s' }) => { const DEFAULT_SPEED = 50; // pixels per second for uniform, calm scrolling
const MarqueeText = ({ text, className, style, speed = DEFAULT_SPEED, duration }) => {
const containerRef = useRef(null); const containerRef = useRef(null);
const textRef = useRef(null); const textRef = useRef(null);
const [isOverflowing, setIsOverflowing] = useState(false); const [isOverflowing, setIsOverflowing] = useState(false);
const [computedDuration, setComputedDuration] = useState('20s');
const formattedDuration = typeof duration === 'number' ? `${duration}s` : duration;
useEffect(() => { useEffect(() => {
const checkOverflow = () => { const updateMarquee = () => {
if (containerRef.current && textRef.current) { if (containerRef.current && textRef.current) {
setIsOverflowing(textRef.current.scrollWidth > containerRef.current.clientWidth); const containerWidth = containerRef.current.clientWidth;
const textWidth = textRef.current.scrollWidth;
const overflowing = textWidth > containerWidth;
setIsOverflowing(overflowing);
if (overflowing) {
if (duration) {
setComputedDuration(typeof duration === 'number' ? `${duration}s` : duration);
} else {
const totalDistance = textWidth + containerWidth;
const seconds = totalDistance / speed;
setComputedDuration(`${seconds.toFixed(2)}s`);
}
}
} }
}; };
checkOverflow();
window.addEventListener('resize', checkOverflow); updateMarquee();
return () => window.removeEventListener('resize', checkOverflow); window.addEventListener('resize', updateMarquee);
}, [text]); const timer = setTimeout(updateMarquee, 100);
return () => {
window.removeEventListener('resize', updateMarquee);
clearTimeout(timer);
};
}, [text, speed, duration]);
return ( return (
<div className={`marquee-container ${className || ''}`} ref={containerRef} style={{ ...style, overflow: 'hidden', whiteSpace: 'nowrap', width: '100%', flex: '1 1 0' }}> <div className={`marquee-container ${className || ''}`} ref={containerRef} style={{ ...style, overflow: 'hidden', whiteSpace: 'nowrap', width: '100%', flex: '1 1 0' }}>
@@ -26,7 +45,7 @@ const MarqueeText = ({ text, className, style, duration = '10s' }) => {
ref={textRef} ref={textRef}
style={{ style={{
display: 'inline-block', display: 'inline-block',
animation: isOverflowing ? `marquee-scroll ${formattedDuration} linear infinite` : 'none', animation: isOverflowing ? `marquee-scroll ${computedDuration} linear infinite` : 'none',
paddingLeft: isOverflowing ? '100%' : '0', paddingLeft: isOverflowing ? '100%' : '0',
fontSize: 'inherit', fontSize: 'inherit',
fontWeight: 'inherit', fontWeight: 'inherit',
@@ -356,8 +356,8 @@
justify-content: space-between; justify-content: space-between;
box-sizing: border-box; box-sizing: border-box;
padding-left: 24px; padding-left: 24px;
padding-right: 12px; padding-right: 14px;
background: linear-gradient(180deg, #13571d 0%, #0a2d0f 100%) !important; background: linear-gradient(180deg, #165b21 0%, #0a2d0f 100%) !important;
position: relative; position: relative;
z-index: 25; z-index: 25;
border-top: 1.5px solid rgba(255, 255, 255, 0.18); border-top: 1.5px solid rgba(255, 255, 255, 0.18);
@@ -383,22 +383,22 @@
.aff-classiques-arrivees__bottom-clock { .aff-classiques-arrivees__bottom-clock {
display: inline-flex; display: inline-flex;
align-items: center; align-items: flex-start;
justify-content: center; justify-content: center;
border: 3.5px solid #ffffff; border: 2.5px solid rgba(255, 255, 255, 0.85);
border-radius: 6px; border-radius: 6px;
padding: 4px 16px; padding: 6px 14px 4px 14px;
background: #0d3613; background: rgba(10, 42, 16, 0.45);
font-family: "Achemine", sans-serif; font-family: "Achemine", sans-serif;
flex-shrink: 0; flex-shrink: 0;
margin-right: 6px; margin-right: 4px;
box-sizing: border-box; box-sizing: border-box;
height: 76px; height: 74px;
gap: 12px; gap: 8px;
} }
.aff-classiques-arrivees__bottom-clock-hm { .aff-classiques-arrivees__bottom-clock-hm {
font-size: 54px; font-size: 52px;
font-weight: 700; font-weight: 700;
line-height: 1; line-height: 1;
letter-spacing: 1.5px; letter-spacing: 1.5px;
@@ -406,10 +406,11 @@
} }
.aff-classiques-arrivees__bottom-clock-s { .aff-classiques-arrivees__bottom-clock-s {
font-size: 54px; font-size: 34px;
font-weight: 700; font-weight: 700;
line-height: 1; line-height: 1;
letter-spacing: 1.5px; letter-spacing: 0.5px;
color: var(--yellow) !important; color: var(--yellow) !important;
margin-top: 0; margin-top: 1px;
margin-left: 2px;
} }
@@ -1314,7 +1314,7 @@ export default function ClassiquesArrivees(){
<div className="aff-classiques-arrivees__sub-stops"> <div className="aff-classiques-arrivees__sub-stops">
<MarqueeText <MarqueeText
className="aff-classiques-arrivees__sub-stops-marquee" className="aff-classiques-arrivees__sub-stops-marquee"
duration="38s" speed={50}
text={ text={
<span className="aff-classiques-arrivees__stops-chain"> <span className="aff-classiques-arrivees__stops-chain">
{stopsList.map((stopName, sIdx) => ( {stopsList.map((stopName, sIdx) => (
@@ -356,8 +356,8 @@
justify-content: space-between; justify-content: space-between;
box-sizing: border-box; box-sizing: border-box;
padding-left: 24px; padding-left: 24px;
padding-right: 12px; padding-right: 14px;
background: linear-gradient(180deg, #07418a 0%, #00225e 100%) !important; background: linear-gradient(180deg, #093c83 0%, #001f55 100%) !important;
position: relative; position: relative;
z-index: 25; z-index: 25;
border-top: 1.5px solid rgba(255, 255, 255, 0.18); border-top: 1.5px solid rgba(255, 255, 255, 0.18);
@@ -383,22 +383,22 @@
.aff-classiques-departs__bottom-clock { .aff-classiques-departs__bottom-clock {
display: inline-flex; display: inline-flex;
align-items: center; align-items: flex-start;
justify-content: center; justify-content: center;
border: 3.5px solid #ffffff; border: 2.5px solid rgba(255, 255, 255, 0.85);
border-radius: 6px; border-radius: 6px;
padding: 4px 16px; padding: 6px 14px 4px 14px;
background: #001e40; background: rgba(0, 24, 64, 0.45);
font-family: "Achemine", sans-serif; font-family: "Achemine", sans-serif;
flex-shrink: 0; flex-shrink: 0;
margin-right: 6px; margin-right: 4px;
box-sizing: border-box; box-sizing: border-box;
height: 76px; height: 74px;
gap: 12px; gap: 8px;
} }
.aff-classiques-departs__bottom-clock-hm { .aff-classiques-departs__bottom-clock-hm {
font-size: 54px; font-size: 52px;
font-weight: 700; font-weight: 700;
line-height: 1; line-height: 1;
letter-spacing: 1.5px; letter-spacing: 1.5px;
@@ -406,10 +406,11 @@
} }
.aff-classiques-departs__bottom-clock-s { .aff-classiques-departs__bottom-clock-s {
font-size: 54px; font-size: 34px;
font-weight: 700; font-weight: 700;
line-height: 1; line-height: 1;
letter-spacing: 1.5px; letter-spacing: 0.5px;
color: var(--yellow) !important; color: var(--yellow) !important;
margin-top: 0; margin-top: 1px;
margin-left: 2px;
} }
@@ -1311,7 +1311,7 @@ export default function ClassiquesDeparts(){
<div className="aff-classiques-departs__sub-stops"> <div className="aff-classiques-departs__sub-stops">
<MarqueeText <MarqueeText
className="aff-classiques-departs__sub-stops-marquee" className="aff-classiques-departs__sub-stops-marquee"
duration="38s" speed={50}
text={ text={
<span className="aff-classiques-departs__stops-chain"> <span className="aff-classiques-departs__stops-chain">
{stopsList.map((stopName, sIdx) => ( {stopsList.map((stopName, sIdx) => (