RELEASE V5.6.0 : Voici les modifications apportées :

-> REFONTE TOTALE DE TOUS LES AFFICHEURS CLASSIQUES + QUAIS + OPTION TGV

FONCTIONNALITÉS EN COURS DE DÉVELOPPEMENT :

-> ADAPTABILITÉ EN FONCTION DES SITUATIONS
This commit is contained in:
mathis committed 2026-08-31 17:32:58 +02:00
1 parent 9c6ac798f1
commit d8f8ceaa9f
18 files changed
+2324 -533

No files matched your search

@@ -0,0 +1,298 @@
/* =========================================================
BANDEAU COMPOSITION TGV (SNCF QUAI - Conforme Image 1 & 2)
========================================================= */
.aff-tgv-comp__container {
position: absolute;
bottom: 0;
left: 0;
width: 1920px;
height: 240px;
background-color: #4a0e4e; /* Couleur Prune / Aubergine SNCF authentique */
color: #ffffff;
font-family: 'Achemine', 'Avenir', 'Helvetica Neue', Arial, sans-serif;
box-sizing: border-box;
padding: 10px 35px 8px 35px;
display: flex;
flex-direction: column;
justify-content: space-between;
z-index: 25;
overflow: hidden;
}
/* =========================================================
TOP ROW : BOX VOIE + RAMES TGV
========================================================= */
.aff-tgv-comp__top-row {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 135px;
}
/* Box Voie à gauche */
.aff-tgv-comp__track-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 120px;
min-width: 120px;
}
.aff-tgv-comp__track-pill {
background-color: #ffffff;
color: #4a0e4e;
font-size: 19px;
font-weight: 800;
text-transform: uppercase;
padding: 2px 14px;
border-radius: 12px;
line-height: 1.1;
letter-spacing: 0.5px;
}
.aff-tgv-comp__track-letter {
color: #ffffff;
font-size: 64px;
font-weight: 800;
line-height: 1;
margin-top: 4px;
margin-bottom: 4px;
text-align: center;
}
.aff-tgv-comp__track-underline {
width: 65px;
height: 3.5px;
background-color: #ffffff;
border-radius: 2px;
}
/* Conteneur des rames TGV */
.aff-tgv-comp__rames-container {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 40px;
flex: 1;
}
/* Bloc d'une Rame */
.aff-tgv-comp__rame-block {
display: flex;
flex-direction: column;
align-items: center;
}
/* Titre Destination au-dessus de la rame (jaune doré) */
.aff-tgv-comp__dest-title {
color: #f5d130;
font-size: 24px;
font-weight: 700;
line-height: 1.1;
margin-bottom: 6px;
text-align: center;
white-space: nowrap;
}
/* Ligne des numéros de voitures */
.aff-tgv-comp__coaches-row {
display: flex;
align-items: center;
width: 100%;
margin-bottom: 4px;
}
.aff-tgv-comp__coach-num-motrice {
width: 44px;
min-width: 44px;
}
.aff-tgv-comp__coach-num-box {
flex: 1;
min-width: 44px;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
font-size: 19px;
font-weight: 700;
letter-spacing: 0.5px;
}
/* Graphic de la rame TGV */
.aff-tgv-comp__train-graphic {
display: flex;
align-items: center;
height: 32px;
width: 100%;
}
/* Motrice avant */
.aff-tgv-comp__loco-front {
width: 44px;
height: 30px;
background: linear-gradient(180deg, #813988 0%, #5d1d64 100%);
border-top-left-radius: 16px;
border-bottom-left-radius: 12px;
margin-right: 2px;
}
/* Corps des voitures */
.aff-tgv-comp__coaches-body {
display: flex;
height: 30px;
flex: 1;
}
.aff-tgv-comp__coach-segment {
flex: 1;
min-width: 44px;
height: 30px;
background-color: #b776bd;
border-right: 2px solid #ffffff;
position: relative;
}
.aff-tgv-comp__coach-segment:first-child {
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
}
.aff-tgv-comp__coach-segment:last-child {
border-right: none;
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
}
.aff-tgv-comp__coach-segment--bar {
background-color: #c98cd0;
}
/* Motrice arrière */
.aff-tgv-comp__loco-rear {
width: 44px;
height: 30px;
background: linear-gradient(180deg, #813988 0%, #5d1d64 100%);
border-top-right-radius: 16px;
border-bottom-right-radius: 12px;
margin-left: 2px;
}
/* =========================================================
LIGNE POINTILLÉE AVEC REPÈRES DE QUAI (Conforme Image 2)
========================================================= */
.aff-tgv-comp__reperes-line-wrapper {
position: relative;
width: 100%;
height: 28px;
display: flex;
align-items: center;
margin: 2px 0 4px 0;
}
.aff-tgv-comp__dotted-line {
position: absolute;
top: 50%;
left: 0;
right: 0;
border-bottom: 2.5px dotted #f5d130;
transform: translateY(-50%);
z-index: 1;
}
.aff-tgv-comp__reperes-row {
position: relative;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 36px;
width: 100%;
padding-right: 40px;
box-sizing: border-box;
z-index: 2;
}
.aff-tgv-comp__repere-box {
background-color: #00337f;
border: 1.5px solid #ffffff;
border-radius: 4px;
color: #ffffff;
font-weight: 800;
font-size: 21px;
line-height: 1;
width: 32px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
}
/* =========================================================
BOTTOM ROW : MARQUEE + DIGITAL CLOCK
========================================================= */
.aff-tgv-comp__bottom-row {
display: flex;
align-items: center;
justify-content: space-between;
height: 52px;
width: 100%;
}
.aff-tgv-comp__marquee-wrapper {
flex: 1;
overflow: hidden;
white-space: nowrap;
margin-right: 30px;
}
.aff-tgv-comp__marquee-text {
display: inline-block;
color: #ffffff;
font-style: italic;
font-weight: 700;
font-size: 29px;
letter-spacing: 1px;
text-transform: uppercase;
animation: affTgvMarquee 25s linear infinite;
}
@keyframes affTgvMarquee {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(-100%);
}
}
/* Horloge TGV */
.aff-tgv-comp__clock-wrapper {
display: flex;
align-items: center;
}
.aff-tgv-comp__clock {
background-color: #00337f;
border: 2px solid #ffffff;
border-radius: 4px;
padding: 3px 14px;
display: flex;
align-items: baseline;
gap: 4px;
font-family: 'Achemine', 'Helvetica Neue', Arial, sans-serif;
font-size: 34px;
font-weight: 700;
line-height: 1;
}
.aff-tgv-comp__clock-hm {
color: #ffffff;
}
.aff-tgv-comp__clock-s {
color: #f5a623;
margin-left: 2px;
}
@@ -0,0 +1,158 @@
import React, { useEffect, useState } from 'react';
import './TgvCompositionQuai.css';
/**
* Icône Bar / Coupe de verre
*/
function BarGlassIcon({ size = 20, color = '#ffffff' }) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 4L12 14L20 4H4Z" stroke={color} strokeWidth="2.2" strokeLinejoin="round" fill={color} />
<path d="M12 14V21M8 21H16" stroke={color} strokeWidth="2.2" strokeLinecap="round" />
</svg>
);
}
/**
* Horloge digitale avec secondes (conformité Image 1 & 2)
*/
function TgvDigitalClock() {
const [now, setNow] = useState(new Date());
useEffect(() => {
const id = setInterval(() => setNow(new Date()), 1000);
return () => clearInterval(id);
}, []);
const hh = String(now.getHours()).padStart(2, '0');
const mm = String(now.getMinutes()).padStart(2, '0');
const ss = String(now.getSeconds()).padStart(2, '0');
return (
<div className="aff-tgv-comp__clock">
<span className="aff-tgv-comp__clock-hm">{hh}:{mm}</span>
<span className="aff-tgv-comp__clock-s">{ss}</span>
</div>
);
}
/**
* Dessin d'une rame TGV de 8 voitures avec motrice avant, arrière et voiture Bar en position 4
*/
function TgvRameShape({ destination, coaches = ['01', '02', '03', 'bar', '05', '06', '07', '08'] }) {
return (
<div className="aff-tgv-comp__rame-block">
{destination && (
<div className="aff-tgv-comp__dest-title">
{destination}
</div>
)}
{/* Numéros des voitures */}
<div className="aff-tgv-comp__coaches-row">
{/* Espace motrice avant */}
<div className="aff-tgv-comp__coach-num-motrice" />
{coaches.map((c, idx) => (
<div key={idx} className="aff-tgv-comp__coach-num-box">
{c === 'bar' ? (
<BarGlassIcon size={20} color="#ffffff" />
) : (
<span>{c}</span>
)}
</div>
))}
{/* Espace motrice arrière */}
<div className="aff-tgv-comp__coach-num-motrice" />
</div>
{/* Silhouette du train TGV */}
<div className="aff-tgv-comp__train-graphic">
{/* Motrice avant arrondie */}
<div className="aff-tgv-comp__loco-front" />
{/* Voitures 1 à 8 */}
<div className="aff-tgv-comp__coaches-body">
{coaches.map((c, idx) => (
<div
key={idx}
className={`aff-tgv-comp__coach-segment ${c === 'bar' ? 'aff-tgv-comp__coach-segment--bar' : ''}`}
/>
))}
</div>
{/* Motrice arrière arrondie */}
<div className="aff-tgv-comp__loco-rear" />
</div>
</div>
);
}
export default function TgvCompositionQuai({
platform = '',
destination = '',
destination2 = '',
marqueeMessage = '',
composition = null,
reperes = ['R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
}) {
const cleanTrack = String(platform || '').replace(/^(?:voie|quai|track|platform)\s*/i, '').trim();
// Détermination des tranches (Rame 1 et Rame 2)
const isMultiDest = Boolean(destination2 && destination2.trim() !== '' && destination2.toLowerCase() !== destination.toLowerCase());
const rame1Coaches = (composition && composition.rame1) || ['01', '02', '03', 'bar', '05', '06', '07', '08'];
const rame2Coaches = (composition && composition.rame2) || ['11', '12', '13', 'bar', '15', '16', '17', '18'];
const defaultMarquee = marqueeMessage || 'MERCI DE VOTRE COMPRÉHENSION..........................................................................................';
return (
<div className="aff-tgv-comp__container">
{/* LIGNE DU HAUT : BOX VOIE + VISUALISATION DU TRAIN */}
<div className="aff-tgv-comp__top-row">
{/* Indicateur Voie à gauche */}
<div className="aff-tgv-comp__track-box">
<div className="aff-tgv-comp__track-pill">Voie</div>
<div className="aff-tgv-comp__track-letter">{cleanTrack ? cleanTrack.toUpperCase() : '-'}</div>
<div className="aff-tgv-comp__track-underline" />
</div>
{/* Visualisation Composition des Rames */}
<div className="aff-tgv-comp__rames-container">
<TgvRameShape
destination={destination}
coaches={rame1Coaches}
/>
<TgvRameShape
destination={isMultiDest ? destination2 : destination}
coaches={rame2Coaches}
/>
</div>
</div>
{/* LIGNE POINTILLÉE AVEC REPÈRES DE QUAI (Conforme Image 2) */}
<div className="aff-tgv-comp__reperes-line-wrapper">
<div className="aff-tgv-comp__dotted-line" />
{reperes && reperes.length > 0 && (
<div className="aff-tgv-comp__reperes-row">
{reperes.map((rep, idx) => (
<div key={idx} className="aff-tgv-comp__repere-box">
{rep}
</div>
))}
</div>
)}
</div>
{/* LIGNE DU BAS : DÉFILEMENT MESSAGE + HORLOGE */}
<div className="aff-tgv-comp__bottom-row">
<div className="aff-tgv-comp__marquee-wrapper">
<div className="aff-tgv-comp__marquee-text">
{defaultMarquee}
</div>
</div>
<div className="aff-tgv-comp__clock-wrapper">
<TgvDigitalClock />
</div>
</div>
</div>
);
}