:root {
    --fond: #ffffff;
    --fond-secondaire: #f0f2f6;
    --texte: #262730;
    --texte-attenue: #555b66;
    --bordure: #e0e2e6;
    --accent: #b01678;
    --pastille-fond: rgba(33, 197, 94, 0.16);
    --pastille-texte: #16a34a;
}

@media (prefers-color-scheme: dark) {
    :root {
        --fond: #0e1117;
        --fond-secondaire: #262730;
        --texte: #f0f2f6;
        --texte-attenue: #a3a8b4;
        --bordure: #3a3d46;
        --accent: #c21758;
        --pastille-fond: rgba(34, 197, 94, 0.2);
        --pastille-texte: #4ade80;
    }
}

/* Choix manuel (icône ⚙ > thème sombre, voir base.html), mémorisé en
localStorage et appliqué via un attribut sur <html> — priment sur la
préférence système ci-dessus (même jeu de variables, dupliqué : ce sont ces
règles qui gagnent quand data-theme est présent, la spécificité seule ne
suffit pas à départager deux sélecteurs :root, c'est l'ordre dans le
fichier qui tranche). Sans attribut (première visite, aucun choix
enregistré), seule la règle @media ci-dessus s'applique. */
:root[data-theme="dark"] {
    --fond: #0e1117;
    --fond-secondaire: #262730;
    --texte: #f0f2f6;
    --texte-attenue: #a3a8b4;
    --bordure: #3a3d46;
    --accent: #c21758;
    --pastille-fond: rgba(34, 197, 94, 0.2);
    --pastille-texte: #4ade80;
}

:root[data-theme="light"] {
    --fond: #ffffff;
    --fond-secondaire: #f0f2f6;
    --texte: #262730;
    --texte-attenue: #555b66;
    --bordure: #e0e2e6;
    --accent: #b01678;
    --pastille-fond: rgba(33, 197, 94, 0.16);
    --pastille-texte: #16a34a;
}

* {
    box-sizing: border-box;
}

input[type="checkbox"] {
    accent-color: var(--accent);
    width: 0.8rem;
    height: 0.8rem;
    cursor: pointer;
}

/* Barres de défilement : par défaut au style natif du système (gris,
identique clair/sombre) — recolorées avec les variables du thème pour
rester cohérentes avec le reste de l'appli. scrollbar-color/-width pour
Firefox, ::-webkit-scrollbar pour Chromium/Safari — s'applique à n'importe
quelle zone défilante (ex: .zone-scroll tbody, le tableau) sans avoir à
cibler chacune séparément. */
* {
    scrollbar-width: thin;
    scrollbar-color: var(--bordure) transparent;
}

*::-webkit-scrollbar {
    width: 10px;
    height: 10px;
}

*::-webkit-scrollbar-track {
    background: transparent;
}

*::-webkit-scrollbar-thumb {
    background: var(--bordure);
    border-radius: 5px;
}

*::-webkit-scrollbar-thumb:hover {
    background: var(--texte-attenue);
}

/* Palliatif : Plotly.js pose sa règle .js-plotly-plot .main-svg{position:
absolute} (superpose ses 2-3 SVG internes — fond, tracés, légende/
annotations — au même endroit) en ajoutant la classe "js-plotly-plot" au
conteneur du graphique. Problème repéré en pratique (Playwright,
2026-08-05) : après un échange htmx (ex: changement de période, qui
recrée #graphique-plot via un nouveau fragment), htmx efface cette classe
juste après le rendu — sa phase de "settle" restaure le classList qu'il
avait mémorisé du conteneur AU MOMENT DU SWAP, soit avant que Plotly.
newPlot() ait eu la main pour y ajouter "js-plotly-plot" (repéré via un
MutationObserver : le changement de classe coïncide exactement avec
l'événement htmx:afterSettle). Résultat sans ce palliatif : les 3 SVG
internes ne sont plus superposés mais empilés en flux normal, avec la
légende/les annotations qui se retrouvent ~700px sous le graphique. Cibler
l'id #graphique-plot (jamais touché par htmx) plutôt que cette classe
contourne le problème à la racine plutôt que d'essayer de gagner la course
avec la phase de settle de htmx.

Même classe wipée, deuxième symptôme repéré en pratique (Playwright,
2026-08-06, onglet Suivi d'un train) : Plotly pose aussi
.js-plotly-plot .main-svg{pointer-events:none} pour que seuls les éléments
interactifs (légende, points survolables — ils portent leur propre
pointer-events="all" en attribut SVG) captent les clics ; les autres SVG
(notamment le hoverlayer, vide mais superposé par-dessus tout le reste)
laissent passer. Sans ce pointer-events:none, le hoverlayer du dessus
intercepte tous les clics, y compris ceux destinés à la légende en
dessous — ici, cliquer une entrée de légende pour masquer une trace ne
faisait plus rien. */
#graphique-plot .main-svg,
#train-plot .main-svg {
    position: absolute;
    top: 0;
    left: 0;
    pointer-events: none;
}

/* Même classe wipée, troisième symptôme repéré en pratique (Playwright,
2026-08-10) : Plotly pose aussi .js-plotly-plot .modebar{position:absolute;
top:2px;right:2px} pour sa barre d'outils (zoom/pan/appareil photo...).
Sans ce palliatif, un changement de période (onglet Graphique) faisait
retomber la barre d'outils en flux normal, agglutinée en haut à gauche du
graphique au lieu d'être ancrée en haut à droite. */
#graphique-plot .modebar,
#train-plot .modebar {
    position: absolute;
    top: 2px;
    right: 2px;
}

/* Toujours la même classe wipée : une fois la barre d'outils replacée en
haut à droite (ci-dessus), ses boutons restaient empilés verticalement au
lieu d'être en ligne — .modebar-group{float:left} est ce qui les aligne
côte à côte, .modebar-btn/.modebar-btn svg leur mise en forme individuelle
(taille, curseur, alignement de l'icône) ; les trois viennent aussi de
.js-plotly-plot, repéré en pratique (Playwright, 2026-08-10). */
#graphique-plot .modebar-group,
#train-plot .modebar-group {
    float: left;
    /* display: inline-block sciemment omis : présent dans la feuille de
    style injectée par Plotly (copiée ici à l'identique), mais sans effet —
    float != none force déjà l'affichage en bloc, l'inline-block d'origine
    est ignoré par les navigateurs (repéré par le linter CSS, 2026-08-12). */
    box-sizing: border-box;
    padding-left: 8px;
    position: relative;
    vertical-align: middle;
    white-space: nowrap;
}

#graphique-plot .modebar-btn,
#train-plot .modebar-btn {
    position: relative;
    font-size: 16px;
    padding: 3px 4px;
    height: 22px;
    cursor: pointer;
    line-height: normal;
    box-sizing: border-box;
}

#graphique-plot .modebar-btn svg,
#train-plot .modebar-btn svg {
    position: relative;
    top: 2px;
}

/* La page elle-même ne défile jamais (voir #zone-contenu plus bas, qui
distribue la hauteur restante à la zone active) : sans ça, une zone qui
déborde un peu (entête sur plusieurs lignes à cause d'un message de statut
long, graphique + texte sous lui plus haut que la fenêtre...) faisait
apparaître une deuxième barre de défilement, celle de la page entière, en
plus de celle déjà prévue pour le tableau/graphique — repéré par
l'utilisateur, 2026-08-06. */
html, body {
    height: 100%;
    overflow: hidden;
}

body {
    margin: 0;
    font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    background: var(--fond);
    color: var(--texte);
    display: flex;
    flex-direction: column;
}

#entete {
    /* position: relative (pas sticky : inutile désormais, la page ne
    défile plus) : garde #bouton-parametres (position: absolute) ancré à
    cette boîte plutôt qu'à la fenêtre entière. */
    position: relative;
    flex-shrink: 0;
    background: var(--fond);
    padding: 0.1rem 1.5rem;
}

h1 {
    margin: 0 0 0.5rem;
    font-size: 1.1rem;
    text-align: center;
}

#bouton-aide,
#bouton-parametres {
    position: absolute;
    top: 0.5rem;
    width: 1.8rem;
    height: 1.8rem;
    padding: 0;
    border: 1px solid var(--bordure);
    border-radius: 0.4rem;
    background: var(--fond-secondaire);
    color: var(--texte);
    font-size: 1rem;
    line-height: 1;
    cursor: pointer;
}

#bouton-parametres {
    right: 1.5rem;
}

/* À gauche du bouton ⚙ (même largeur + un peu d'espace), pas dans son menu
   déroulant : demande explicite de l'utilisateur, une icône séparée plutôt
   qu'une entrée de plus dans #menu-parametres. */
#bouton-aide {
    right: 3.7rem;
}

#bouton-aide:hover,
#bouton-parametres:hover {
    border-color: var(--accent);
}

#menu-parametres {
    position: fixed;
    top: 2.5rem;
    left: auto;
    right: 1.5rem;
    margin: 0;
    padding: 0.75rem 1rem;
    border: 1px solid var(--bordure);
    border-radius: 0.5rem;
    background: var(--fond);
    color: var(--texte);
}

/* :popover-open, pas juste #menu-parametres : un display:flex "en dur" (non
conditionné à cet état) l'emporterait sur le display:none par défaut du
navigateur pour un popover fermé — le menu resterait alors affiché en
permanence, plus de vrai bascule ouvert/fermé (repéré par l'utilisateur,
2026-08-06). */
#menu-parametres:popover-open {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.6rem;
}

#menu-parametres button {
    padding: 0.35rem 0.7rem;
    border: 1px solid var(--bordure);
    border-radius: 0.4rem;
    background: var(--fond-secondaire);
    color: var(--texte);
    font-size: 0.8rem;
    cursor: pointer;
    white-space: nowrap;
}

#menu-parametres button:hover {
    border-color: var(--accent);
}

.switch-theme {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.85rem;
    white-space: nowrap;
    cursor: pointer;
}

#controles {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 1rem;
    margin-bottom: 0.7rem;
    font-size: 0.8rem;
    justify-content: space-around;
}

#controles button,
#controles a {
    padding: 0.35rem 0.7rem;
    border: 1px solid var(--bordure);
    border-radius: 0.4rem;
    background: var(--fond-secondaire);
    color: var(--texte);
    font-size: 0.8rem;
    cursor: pointer;
    text-decoration: none;
    line-height: 1.2;
}

#controles button:hover,
#controles a:hover {
    border-color: var(--accent);
}

.controle-case {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--texte);
    white-space: nowrap;
}

/* Bulle générique de message ponctuel (afficherToast/masquerToast,
   base.html), 4 types — couleurs réutilisant la palette déjà en place
   plutôt que des teintes fixes non thémées : succès reprend
   --pastille-fond/texte (déjà utilisés pour le badge "circulations
   perturbées"), erreur/avertissement reprennent le rouge/orange déjà
   utilisés pour tr.retard-fort/tr.retard-moyen (Tableau, plus bas dans ce
   fichier) — mêmes teintes fixes (pas de variante sombre dédiée) que ces
   deux règles, déjà lisibles sur les deux thèmes en pratique. */
#toast {
    position: fixed;
    left: 0.5rem;
    top: 0.5rem;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 1rem;
    border-radius: 0.5rem;
    border: 1px solid transparent;
    font-size: 0.85rem;
    max-width: 24rem;
    cursor: pointer;
    opacity: 0;
    pointer-events: none;
    z-index: 10;
}

#toast.visible {
    opacity: 1;
    pointer-events: auto;
    animation: toast-entree 0.2s ease forwards;
}

#toast.sortie {
    animation: toast-sortie 0.2s ease forwards;
}

@keyframes toast-entree {
    from { opacity: 0; transform: translateX(-12px); }
    to { opacity: 1; transform: translateX(0); }
}

@keyframes toast-sortie {
    from { opacity: 1; transform: translateX(0); }
    to { opacity: 0; transform: translateX(-12px); }
}

.toast-icone {
    font-weight: bold;
}

#toast.toast-succes {
    background: var(--pastille-fond);
    color: var(--pastille-texte);
    border-color: var(--pastille-texte);
}

#toast.toast-erreur {
    background: rgba(239, 68, 68, 0.12);
    color: #ef4444;
    border-color: #ef4444;
}

#toast.toast-avertissement {
    background: rgba(249, 115, 22, 0.12);
    color: #f97316;
    border-color: #f97316;
}

#toast.toast-info {
    background: var(--fond-secondaire);
    color: var(--texte);
    border-color: var(--bordure);
}

/* Contenu du guide statistiques (texte porté depuis generer_guide_
   statistiques.py, GUIDE_PAGES — pas d'import Python direct, ce module
   entraîne matplotlib au chargement, évité côté app_fastapi.py comme pour
   TRAJET_COLORMAP/couleur_degradee ailleurs dans ce fichier) + "Sources des
   données" (regroupées ici, retirées du menu ⚙ — demande explicite de
   l'utilisateur, 2026-08-06). Beaucoup de contenu (~14 sections + sources) :
   hauteur plafonnée avec défilement interne plutôt que de déborder de
   l'écran, et position: relative pour ancrer la croix de fermeture. */
dialog#dialogue-aide {
    position: relative;
    max-width: 42rem;
    max-height: 85vh;
    overflow-y: auto;
    border: 2px solid var(--bordure);
    background: var(--fond);
    color: var(--texte);
    padding: 1.25rem 1.75rem;
}

dialog#dialogue-aide::backdrop {
    background: rgba(0, 0, 0, 0.4);
}

dialog#dialogue-aide h2 {
    margin-top: 0;
    font-size: 1.1rem;
}

/* Collée en haut (position: sticky, reste visible pendant le défilement du
   dialogue), alignée à droite via margin-left: auto plutôt qu'un float
   (peu fiable combiné à sticky) : premier élément focusable du dialogue,
   pas le bouton "Fermer" tout en bas — sans elle, showModal() focalisait
   ce bouton par défaut et le dialogue s'ouvrait déjà défilé jusqu'en bas
   (repéré par l'utilisateur, 2026-08-06). */
.dialogue-fermer {
    position: sticky;
    top: 0;
    display: block;
    margin-left: auto;
    margin-bottom: -1.6rem;
    width: 1.6rem;
    height: 1.6rem;
    border: 1px solid var(--bordure);
    border-radius: 0.4rem;
    background: var(--fond-secondaire);
    color: var(--texte);
    font-size: 1.1rem;
    line-height: 1;
    cursor: pointer;
}

.dialogue-fermer:hover {
    border-color: var(--accent);
}

/* Paragraphes "Sources des données" nus (pas dans .aide-section, contenu
   plus court/plat) — même gabarit que .aide-section > p. */
dialog#dialogue-aide > p {
    font-size: 0.85rem;
    margin: 0.5rem 0;
}

dialog#dialogue-aide .aide-theme {
    margin: 1.5rem 0 0.75rem;
    padding-bottom: 0.3rem;
    border-bottom: 1px solid var(--bordure);
    font-size: 0.95rem;
    color: var(--accent);
}

.aide-section {
    margin-bottom: 1.25rem;
}

.aide-section h4 {
    margin: 0 0 0.35rem;
    font-size: 0.9rem;
    color: var(--accent);
}

.aide-sous-titre {
    font-weight: normal;
    font-size: 0.8rem;
    color: var(--texte-attenue);
}

.aide-section > p {
    margin: 0.35rem 0;
    font-size: 0.85rem;
    line-height: 1.45;
}

.aide-exemple {
    margin: 0.5rem 0;
    padding: 0.5rem 0.7rem;
    background: var(--fond-secondaire);
    border-radius: 0.3rem;
    font-family: monospace;
    font-size: 0.78rem;
    line-height: 1.5;
}

.aide-utilite {
    font-weight: 600;
}

.aide-pourquoi {
    color: var(--texte-attenue);
    font-style: italic;
}

#filtres {
    display: flex;
    flex-wrap: wrap;
    align-items: end;
    gap: 1.5rem;
    margin-bottom: 0.5rem;
    justify-content: space-around;
}

.champ {
    display: flex;
    gap: 0.25rem;
    font-size: 0.85rem;
    align-items:center;
}

.champ label {
    color: var(--texte-attenue);
}

.champ select {
    padding: 0.35rem 0.5rem;
    margin-top: 0.1rem;
    border: 1px solid var(--bordure);
    border-radius: 0.4rem;
    background: var(--fond-secondaire);
    color: var(--texte);
    min-width: 10rem;
}

.champ-case {
    flex-direction: row;
    align-items: center;
}

.champ-case label {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--texte);
    white-space: nowrap;
}

#stats {
    display: flex;
    flex-wrap: wrap;
    gap: 2rem;
    padding-bottom: 0.2rem;
    justify-content: space-around;
    background: var(--fond-secondaire);
}

.stat-label {
    font-size: 0.8rem;
    color: var(--texte-attenue);
}

.stat-valeur {
    display: flex;
    align-items: center;
    font-size: 0.9rem;
    font-weight: 600;
}

.pastille {
    /* display:flex + align-items:center sur .stat-valeur (ci-dessus)
    plutôt que vertical-align:middle : ce dernier aligne sur le milieu de
    la ligne de texte environnante, peu fiable dès que la pastille a une
    taille de police/un padding différents du texte à côté — la badge
    "23 %" retombait visiblement plus bas que "699 / 3049" (repéré par
    l'utilisateur, 2026-08-10). */
    display: inline-block;
    margin-left: 0.4rem;
    padding: 0.05rem 0.5rem;
    border-radius: 0.5rem;
    background: var(--pastille-fond);
    color: var(--pastille-texte);
    font-size: 0.75rem;
    font-weight: 600;
}

.info {
    font-size: 0.75rem;
    color: var(--texte-attenue);
    text-align:center;
}

#onglets {
    display: flex;
    gap: 0.25rem;
    margin-top: 0.7rem;
    justify-content: center;
}

.onglet {
    padding: 0.5rem 1rem;
    border: none;
    background: var(--fond-secondaire);
    color: var(--texte-attenue);
    font-size: 0.9rem;
    cursor: pointer;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
}

.onglet:hover {
    color: var(--texte);
}

.onglet.actif {
    color: var(--accent);
    border-bottom-color: var(--accent);
    font-weight: 600;
}

#zone-contenu {
    /* flex: 1 + min-height: 0 : occupe tout l'espace restant sous #entete
    (et au-dessus du futur #pied-de-page), quelle que soit leur hauteur
    réelle — remplace un calc(100vh - Npx) figé, qui doit être recalculé à
    la main à chaque fois que la hauteur de l'entête change (voir
    l'ancienne valeur sur .zone-scroll tbody, remplacée par ce mécanisme).
    min-height: 0 est nécessaire ici : un enfant flex ne peut sinon jamais
    devenir plus petit que son contenu, ce qui empêcherait tout défilement
    interne. overflow: hidden : le vrai défilement se fait plus bas, dans
    .zone-scroll/.zone-graphique. */
    flex: 1;
    min-height: 0;
    overflow: hidden;
    padding: 0.5rem 1.5rem;
    display: flex;
    flex-direction: column;
}

#pied-de-page {
    /* La frise (vue d'ensemble de la ligne, .zone-frise ci-dessous) a une
    hauteur fixe connue à l'avance — #zone-contenu (flex: 1 plus haut)
    s'adapte automatiquement autour, sans recalcul manuel. Fond légèrement
    distinct (même variable que thead th/#menu-parametres) + bordure du
    haut : sépare visuellement le pied de page du contenu défilant
    au-dessus (demande explicite de l'utilisateur, 2026-08-10). */
    flex-shrink: 0;
    background: var(--fond-secondaire);
    border-top: 1px solid var(--bordure);
}

.zone-frise {
    padding: 0.2rem 1.5rem 0.4rem;
}

.frise-titre {
    margin: 0;
    font-size: 0.8rem;
    font-weight: 600;
}

/* Icône ℹ ronde générique, tooltip via title="" natif — introduite pour la
frise, réutilisée ailleurs (ex: barre de stats générales) dès qu'un libellé
a besoin d'une explication sans place pour un texte permanent. */
.icone-info {
    cursor: help;
    color: var(--texte-attenue);
    font-weight: 400;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.2em;
    height: 1.2em;
    border: 1px solid var(--texte-attenue);
    border-radius: 50%;
    font-size: 0.75em;
    line-height: 1;
}

/* Défilement horizontal plutôt qu'un texte illisible : 11 étiquettes de
gares ne peuvent pas toutes tenir lisiblement sous une largeur d'écran
donnée (viewer.py agrandissait la fenêtre Tkinter pour compenser,
_ajuster_largeur_fenetre — non transposable au web). Même principe que
.zone-scroll pour les tableaux. */
.frise-scroll {
    overflow-x: auto;
}

.frise-svg {
    /* aspect-ratio (pas une hauteur fixe en px) : avec
    preserveAspectRatio="none" (_frise.html), une largeur responsive
    combinée à une hauteur figée étire le viewBox différemment en x et en
    y, ce qui rendait les points des gares légèrement ovales plutôt que
    ronds (repéré par l'utilisateur, 2026-08-10) — même ratio que le
    viewBox impose un facteur d'échelle identique sur les deux axes, donc
    des cercles qui restent des cercles quelle que soit la largeur réelle.
    Doit rester synchronisé avec le viewBox de _frise.html (1000x55) ET
    avec FRISE_Y_LIGNE (app_fastapi.py) et les tailles de police ci-dessous
    (.frise-nom/-retard/-legende/-connecteur-texte) : la taille du texte
    est en px CSS réels, donc ne suit PAS l'échelle du viewBox — réduire la
    hauteur sans réduire aussi la police fait mécaniquement grossir le
    texte par rapport aux points/lignes (repéré par l'utilisateur,
    2026-08-10, en cherchant un compromis hauteur/police). */
    display: block;
    width: 100%;
    min-width: 700px;
    aspect-ratio: 1000 / 55;
}

/* Couleurs de statut (rouge/orange/vert/gris) posées en attribut fill
directement par le backend (calculer_contexte_frise, app_fastapi.py) —
valeurs fixes, pas liées au thème, comme tr.retard-fort/-moyen plus haut.
Tout le reste ci-dessous (lignes, connecteurs, textes, points hors-trajet)
utilise les variables de thème : contrairement à viewer.py (app desktop
sans mode sombre), cette version a un vrai thème sombre, donc les gris
fixes de l'original seraient illisibles sur fond sombre — et comme ce SVG
est rendu côté serveur (pas du Plotly/JS), il suit le thème automatiquement
au clic sur le bouton sombre/clair, sans le redraw JS qu'il a fallu ajouter
pour les graphiques Plotly. */
.frise-point {
    stroke: var(--bordure);
    stroke-width: 1;
}

.frise-point-estompe {
    fill: var(--fond-secondaire);
}

.frise-nom {
    fill: var(--texte);
    font-size: 7px;
}

.frise-nom-estompe {
    fill: var(--texte-attenue);
    font-size: 7px;
}

.frise-retard {
    fill: var(--texte);
    font-size: 7px;
    font-weight: 600;
}

.frise-legende {
    fill: var(--texte-attenue);
    font-size: 7px;
    font-style: italic;
}

.frise-ligne-fond {
    stroke: var(--bordure);
    stroke-width: 2;
}

.frise-ligne-fond-estompee {
    stroke: var(--bordure);
    stroke-width: 2;
    opacity: 0.5;
}

.frise-ligne-trajet {
    stroke: var(--texte);
    stroke-width: 3;
}

.frise-connecteur {
    stroke: var(--texte-attenue);
    stroke-width: 1;
    stroke-dasharray: 4 2;
}

.frise-connecteur-texte {
    fill: var(--texte);
    font-size: 6px;
    font-weight: 600;
}

.frise-fleche {
    fill: var(--texte-attenue);
}

/* thead/tbody affichés en display:block (au lieu du display:table-header-
group/table-row-group natif) pour que seul tbody défile, pas thead — sinon
la barre de défilement native de .zone-scroll couvre toute sa hauteur, y
compris l'entête, même si celui-ci reste visuellement figé en haut (repéré
par l'utilisateur, 2026-08-05 : la barre commençait au niveau de l'entête
au lieu de commencer au niveau des données). Chaque <tr> repasse en
display:table (table-layout:fixed) pour continuer à répartir ses cellules
en colonnes — thead et tbody n'étant plus dans le même contexte de tableau,
la largeur de chaque colonne doit être fixée explicitement (voir th/td
nth-child plus bas) pour que les deux s'alignent malgré tout. */
.zone-scroll {
    /* Remplit tout l'espace laissé par #zone-contenu (flex: 1 ci-dessus)
    plutôt qu'un calc(100vh - Npx) figé — voir la note sur #zone-contenu. */
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

.zone-scroll table {
    table-layout: fixed;
    display: flex;
    flex-direction: column;
    height: 100%;
}

.zone-scroll thead,
.zone-scroll tbody {
    display: block;
    width: 100%;
}

.zone-scroll thead tr,
.zone-scroll tbody tr {
    display: table;
    table-layout: fixed;
    width: 100%;
}

.zone-scroll thead {
    flex-shrink: 0;
}

.zone-scroll tbody {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}

/* Même rôle que .zone-scroll côté Tableau, pour les 3 onglets basés sur un
graphique (pas de tableau à faire défiler en interne, juste tout le bloc
onglet) : remplit l'espace laissé par #zone-contenu et défile en interne
(sélecteur + graphique + stats en dessous peuvent dépasser la fenêtre, ex:
fenêtre basse) au lieu de faire défiler la page entière. Règles identiques
regroupées (étaient dupliquées 3x), audit du 2026-08-10. */
.zone-graphique,
.zone-train,
.zone-jour-heure {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}

.ligne-periode {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.75rem;
    justify-content: space-around;
}

/* .champ a son propre font-size (0.85rem), qui l'emporte sur l'héritage
de .ligne-periode ci-dessus (une règle explicite sur l'élément gagne
toujours face à une valeur seulement héritée du parent) — sans ce
correctif, le 0.75rem de .ligne-periode ne s'appliquait quasiment jamais
en pratique puisque tout son contenu est enveloppé dans .champ/.champ-case
(repéré par l'utilisateur, 2026-08-10). Scopé à .ligne-periode pour ne pas
toucher .champ ailleurs (ex: les filtres Gare/Train/Sens de l'entête). */
.ligne-periode .champ {
    font-size: 0.8rem;
}

.ligne-periode .info {
    margin: 0;
}

/* Stats propres à l'onglet Graphique (remplacent la ligne de stats
   générales masquée sur cet onglet, voir #stats dans base.html) — même
   poids que .stat-valeur, pour rester visuellement cohérent avec elles. */
.stat-graphique {
    font-weight: 600;
}

/* Icône ⏱ de la durée théorique (Suivi d'un train) légèrement agrandie par
rapport au texte autour, pour ressortir un peu sans être criarde. */
.duree-icone {
    font-size: 1.3em;
}

/* #graphique-plot/#train-plot : même taille, regroupées (audit 2026-08-10).
#jh-* (Par jour/heure) n'a pas d'équivalent ici : chaque graphique fait
32vh via .grille-jour-heure > div plus bas, pas 65vh plein. */
#graphique-plot,
#train-plot {
    width: 100%;
    height: 65vh;
}

/* 2 colonnes × 3 lignes, comme les add_subplot(321..326) de viewer.py — 6
   graphiques Plotly indépendants (pas un seul multi-panneaux, contrairement
   à Graphique) plutôt qu'un montage à domaines multiples dans une seule
   figure, chacun avec son propre axe catégoriel (jours/heures/2 catégories,
   pas d'axe commun à synchroniser entre eux). */
.grille-jour-heure {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1rem;
}

.grille-jour-heure > div {
    height: 32vh;
    min-height: 260px;
}

.zone-travaux {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    overflow: hidden;
}

.section-travaux {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
}

.travaux-intro {
    font-weight: 600;
    margin: 0.25rem 0 0;
}

.zone-travaux .travaux-source {
    text-align: left;
    margin: 0 0 0.4rem;
}

.resume-travaux {
    color: #c2410c;
    font-weight: 600;
    margin: 0 0 0.5rem;
}

tr.alerte-active {
    font-weight: 600;
}

/* Regroupe tr.alerte-passee/tr.gtfs-echec (règles identiques, audit
2026-08-10). */
tr.alerte-passee,
tr.gtfs-echec {
    color: var(--texte-attenue);
}

/* Base commune aux 3 tableaux à texte libre (contrairement au Tableau
principal, centré/nowrap par défaut plus bas dans ce fichier) — regroupée
ici, .table-gtfs avait la même règle dupliquée plus bas (audit 2026-08-10). */
.table-alertes th, .table-alertes td,
.table-evenements th, .table-evenements td,
.table-gtfs th, .table-gtfs td {
    text-align: left;
    white-space: normal;
}

.table-alertes th:nth-child(1), .table-alertes td:nth-child(1) { width: 20%; }
.table-alertes th:nth-child(2), .table-alertes td:nth-child(2) { width: 13%; }
.table-alertes th:nth-child(3), .table-alertes td:nth-child(3) { width: 13%; }
.table-alertes th:nth-child(4), .table-alertes td:nth-child(4) { width: 54%; }

.table-evenements th:nth-child(1), .table-evenements td:nth-child(1) { width: 28%; }
.table-evenements th:nth-child(2), .table-evenements td:nth-child(2) { width: 12%; }
.table-evenements th:nth-child(3), .table-evenements td:nth-child(3) { width: 40%; }
.table-evenements th:nth-child(4), .table-evenements td:nth-child(4) { width: 20%; }

.zone-gtfs {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    overflow: hidden;
}

#filtres-gtfs {
    margin: 0;
}

tr.gtfs-aggrave {
    color: #c2410c;
    font-weight: 600;
}

.table-gtfs th:nth-child(1), .table-gtfs td:nth-child(1) { width: 14%; }
.table-gtfs th:nth-child(2), .table-gtfs td:nth-child(2) { width: 12%; }
.table-gtfs th:nth-child(3), .table-gtfs td:nth-child(3) { width: 12%; }
.table-gtfs th:nth-child(4), .table-gtfs td:nth-child(4) { width: 8%; }
.table-gtfs th:nth-child(5), .table-gtfs td:nth-child(5) { width: 8%; }
.table-gtfs th:nth-child(6), .table-gtfs td:nth-child(6) { width: 8%; }
.table-gtfs th:nth-child(7), .table-gtfs td:nth-child(7) { width: 8%; }
.table-gtfs th:nth-child(8), .table-gtfs td:nth-child(8) { width: 8%; }
.table-gtfs th:nth-child(9), .table-gtfs td:nth-child(9) { width: 8%; }
.table-gtfs th:nth-child(10), .table-gtfs td:nth-child(10) { width: 14%; }

/* Regroupe .zone-travaux/.zone-gtfs (règles identiques, audit 2026-08-10). */
.zone-travaux details summary,
.zone-gtfs details summary {
    cursor: pointer;
}

.zone-travaux details[open] summary,
.zone-gtfs details[open] summary {
    margin-bottom: 0.25rem;
}

.gtfs-detail {
    margin: 0;
    font-family: Consolas, Menlo, monospace;
    font-size: 0.75rem;
    white-space: pre-wrap;
    color: var(--texte-attenue);
}

/* Surlignage façon surligneur des lignes-titres "Exemples de services
...", posé côté backend (_surligner_exemples_gtfs, app_fastapi.py) —
demande explicite de l'utilisateur, 2026-08-10. */
.gtfs-exemple-titre {
    background: rgba(234, 179, 8, 0.3);
    border-radius: 2px;
    padding: 0 2px;
    font-weight: 600;
    color: var(--texte);
}

table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.8rem;
}

thead th {
    background: var(--fond-secondaire);
    padding: 0.5rem 0.4rem;
    text-align: center;
    border-right: 1px solid var(--bordure);
}

thead th:last-child {
    border-right: none;
}

tbody td {
    padding: 0.25rem 0.4rem;
    text-align: center;
    white-space: nowrap;
}

/* Largeurs explicites par colonne (Relevé, Train, Sens, Gare, Heure théo.,
Arr., Dép., Temp., Pluie, Vent, Jour, Vacances, Arrêts) : nécessaires pour
que thead et tbody s'alignent une fois séparés en deux contextes de tableau
distincts (voir .zone-scroll plus haut) — table-layout:fixed sans largeur
explicite répartirait les colonnes de chaque <tr> indépendamment les unes
des autres. Proportions reprises des largeurs naturelles observées
précédemment (contenu le plus large en premier). */
th:nth-child(1), td:nth-child(1) { width: 11%; }
th:nth-child(2), td:nth-child(2) { width: 12%; }
th:nth-child(3), td:nth-child(3) { width: 10%; }
th:nth-child(4), td:nth-child(4) { width: 11%; }
th:nth-child(5), td:nth-child(5) { width: 8%; }
th:nth-child(6), td:nth-child(6) { width: 6%; }
th:nth-child(7), td:nth-child(7) { width: 6%; }
th:nth-child(8), td:nth-child(8) { width: 6%; }
th:nth-child(9), td:nth-child(9) { width: 8%; }
th:nth-child(10), td:nth-child(10) { width: 8%; }
th:nth-child(11), td:nth-child(11) { width: 4%; }
th:nth-child(12), td:nth-child(12) { width: 6%; }
th:nth-child(13), td:nth-child(13) { width: 4%; }

tr.separateur td {
    padding: 0;
    height: 0.2rem;
    background: var(--fond-secondaire);
}

/* Contrairement à viewer.py (Treeview, colore toute la ligne), ces 3
couleurs de perturbation ne ciblent que les colonnes Arr. (min)/Dép. (min)
(6e/7e colonnes de COLONNES, app_fastapi.py) — demande explicite de
l'utilisateur, 2026-08-10 : le reste de la ligne (train, gare, météo...)
n'a pas besoin d'être coloré pour signaler un retard. tr.hors-ligne
ci-dessous reste sur toute la ligne : ce n'est pas une perturbation, c'est
un indicateur de gare hors ligne. */
tr.retard-fort td:nth-child(6),
tr.retard-fort td:nth-child(7) {
    color: #ef4444;
}

tr.retard-moyen td:nth-child(6),
tr.retard-moyen td:nth-child(7) {
    color: #f97316;
}

/* nth-child(7) seulement (Dép.), pas nth-child(6) (Arr.) contrairement aux
deux règles ci-dessus : couleur_ligne() (app_fastapi.py) ne renvoie
"depart-retard" que quand le retard à l'arrivée n'a déjà rien déclenché
(< SEUIL_RETARD_MOYEN ou inconnu) — colorer aussi Arr. afficherait un "0.0"
bénin en doré, comme si l'arrivée posait aussi problème. Repéré par
l'utilisateur, 2026-08-12. */
tr.depart-retard td:nth-child(7) {
    color: #ccb531;
}

tr.hors-ligne {
    color: #999999;
}
