/* =========================================================================
   SOCAR SHIPPING — SOCLE COMMUN AUX TROIS ÉCRANS
   Cible : TV 55" 16:9, lecture à ~3 m, fonctionnement 24/7 hors ligne.

   Règle d'échelle : tout est exprimé en `rem`. `1rem` vaut 16 px sur un canvas
   de conception 1920x1080 et se recalcule pour remplir n'importe quel écran
   16:9 (1080p, 1440p, 4K) ou pour tenir dans une fenêtre de preview.
   Conséquence : une seule mise en page, aucun media query, aucun débordement.

   Ce fichier contient ce que les trois vues partagent : jetons de couleur,
   en-tête, indicateur de fraîcheur, drapeaux, prix, pastilles, rail des
   départs, bloc de suivi, écran d'échec. La mise en page propre à chaque vue
   vit dans styles.css (tableau), cartes.css ou navires.css.
   ========================================================================= */

/* --- Police embarquée ------------------------------------------------------
   Inter (SIL OFL 1.1), fichiers locaux, aucun appel réseau : un kiosque hors
   ligne doit rendre exactement la même chose qu'un poste connecté.

   Elle remplace Segoe UI comme police PRINCIPALE, pas comme repli. Segoe UI
   n'existe que sous Windows : la machine de dev, le kiosque Linux et la démo
   Cloudflare rendaient trois écrans différents sans que personne le voie.

   Fichier variable, graisses 400 à 800 en un seul téléchargement — l'écran
   utilise 400, 600, 700 et 800. Deux sous-ensembles : `latin` couvre le
   français courant, `latin-ext` les noms de ports et de navires. 130 Ko en
   tout, lus une fois puis mis en cache par le service worker. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("assets/fonts/inter-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("assets/fonts/inter-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
                 U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
                 U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113,
                 U+2C60-2C7F, U+A720-A7FF;
}

:root {
  /* 16/1920*100 = 0.8333vw ; 16/1080*100 = 1.4815vh
     Le `min` garantit deux choses à la fois : la planche fait toujours au
     moins 120rem de large (sa largeur de conception, en dessous de laquelle
     les colonnes ne tiennent plus) et jamais plus de 67.5rem de haut. */
  font-size: min(0.8333vw, 1.4815vh);

  /* --- Fond --------------------------------------------------------------
     Bleu roi SOCAR, relevé au pixel sur l'écran de production
     my.socar.be/grimaldivoyages_screen : la grille y va de #26448C à gauche à
     #182B56 à droite, sur un fond de page plus profond. C'est cette famille
     qu'on reprend, pas une palette voisine — deux écrans SOCAR côte à côte
     dans le même hall doivent se lire comme un seul système.

     Ce qui a changé par rapport au bleu nuit précédent (#020a12) : le noir de
     référence remonte, donc le contraste absolu baisse. On le compense en
     rendant les prix BLANC PUR (#fff, comme en production) au lieu du blanc
     cassé — sur #1f3a78 le rapport reste à 8.2:1, très au-dessus du seuil. */
  --bg:            #0e2354;   /* fond de planche                   */
  --panel:         #1b3a7d;   /* cartes navire, bandeaux           */
  --panel-2:       #24478f;
  --line:          #4d6fb6;
  --line-soft:     #33559d;

  /* Dégradé horizontal de la grille tarifaire, relevé sur l'écran SOCAR. */
  --grid-1:        #26448c;
  --grid-2:        #182b56;

  /* --- Texte -------------------------------------------------------------
     Les prix sont l'information la plus importante : ils sont donc le blanc
     le plus pur de l'écran. Aucune donnée critique n'est portée par la
     couleur de marque (contraste bleu-sur-bleu = la faute des maquettes IA). */
  --ink:           #ffffff;   /* prix, destinations, navires       */
  --ink-2:         #dce6f7;   /* dates                             */
  --ink-3:         #9fb4dc;   /* libellés, unités                  */
  --ink-4:         #6d86bb;   /* tirets « pas de tarif »           */

  --brand:         #4bb4e8;   /* remonté : #1e9cd7 sur bleu roi était sourd */
  --brand-soft:    #8ed6f7;

  /* --- Or SOCAR ----------------------------------------------------------
     Le symbole « € » est jaune sur l'écran de production (#FFFF48 relevé) et
     nulle part ailleurs. On garde exactement ce rôle : il attache l'unité au
     montant sans lui disputer le contraste, et c'est la signature visuelle
     que le hall reconnaît déjà. Il sert aussi au suffixe de service
     (« TRANSIT »), traité en jaune sur l'écran SOCAR pour la même raison.

     Corollaire : aucune ALERTE ne peut plus être jaune, sinon 80 symboles €
     crieraient au danger. C'est pourquoi l'ambre des navires et des remarques
     est passé à l'orange franc ci-dessous. */
  --gold:          #ffff48;   /* valeur exacte relevée sur l'écran SOCAR */

  /* --- Couleurs navires --------------------------------------------------
     Rôle fonctionnel, pas décoratif : elles relient une ligne du tableau à sa
     carte de départ. Toujours doublées d'un libellé texte — la couleur n'est
     jamais le seul canal d'information.

     Remontées d'un cran depuis le passage au fond bleu : sur #020a12 elles
     avaient 9:1, sur #1f3a78 les valeurs d'origine tombaient à 4.3:1.
     `--v-amber` garde son NOM (c'est une valeur de donnée, dans
     agent/data/vessels.json) mais vaut désormais un orange, pour ne pas
     entrer en collision avec l'or du symbole €. */
  --v-cyan:        #5fd0ff;
  --v-green:       #5ff0ad;
  --v-amber:       #ff9f45;
  --v-violet:      #c2abff;
  --v-rose:        #ff8fa6;

  /* --- Sémantique remarques --------------------------------------------- */
  --tag-promo:     #5ff0ad;
  --tag-warning:   #ff9f45;
  --tag-new:       #5fd0ff;
  --tag-info:      #b7c8e8;

  --radius:        0.75rem;

  /* Largeur maximale du contenu. `100%` = la planche remplit la dalle, quelle
     que soit sa proportion. Y écrire une valeur en rem (138rem par exemple)
     rebride le contenu et repeint le surplus en marge. Voir `.board`. */
  --content-max:   100%;

  /* Durée du fondu entre les deux scènes du rail. Posée au démarrage par
     core.js depuis `CONFIG.sceneFadeMs` : cette valeur n'est qu'un repli si
     le script n'a pas encore tourné. */
  --scene-fade:    800ms;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html, body {
  height: 100%;
  /* Plus de noir : la planche remplit désormais toute la fenêtre (voir
     `.board`), et ce fond n'est visible qu'une fraction de seconde au
     chargement. Il vaut mieux qu'il soit déjà de la bonne couleur. */
  background: var(--bg);
  overflow: hidden;
  cursor: none;                       /* kiosque : pas de curseur parasite */
}

body {
  font-family: "Inter", "Segoe UI", system-ui, -apple-system, sans-serif;
  -webkit-font-smoothing: antialiased;
  display: grid;
  place-items: center;
}

/* Canvas de conception : 1920x1080, soit 120rem x 67.5rem.

   LA PLANCHE REMPLIT TOUJOURS LA FENÊTRE. Elle a longtemps eu une taille
   fixe de 120x67.5rem, ce qui produisait des bandes noires dès que la fenêtre
   n'était pas en 16:9 — le cas de toute dalle ultra-large, et le cas de
   n'importe quel navigateur qui garde sa barre d'adresse (1920x1080 moins le
   chrome = un viewport en 2.18:1). L'utilisateur devait dézoomer à la main
   pour cadrer le tableau. Ce n'est plus le cas.

   ELLE OCCUPE AUSSI TOUTE LA LARGEUR. Une version précédente plafonnait le
   contenu à 138rem et versait le surplus en marge, au motif qu'un tableau
   étiré se lit moins bien. Sur une dalle 2560x1080 cela faisait 176 px de
   fond nu de chaque côté : de loin, ça ne se lit pas comme une marge de
   composition mais comme un écran mal réglé. Le plafond est donc levé, et le
   surplus est absorbé là où il sert — la colonne DESTINATION, la colonne
   BATEAU et le rail, dont les poids `fr` ont été relevés en conséquence
   (voir styles.css). Les colonnes de prix n'en prennent presque rien : un
   montant flottant au milieu de son vide se lit moins bien, pas mieux.

   `--content-max` reste le seul réglage : y écrire une largeur (138rem, par
   exemple) rebride la planche et repeint le surplus en marge. Sur une TV
   16:9, la formule rend exactement le 1.1rem d'origine et RIEN ne bouge.

   Le fond n'est pas uni. Deux halos très diffus — un froid derrière le titre,
   un plus profond au coin opposé — donnent une profondeur que l'aplat n'a
   pas, sans jamais passer sous la grille tarifaire : le contraste des prix
   est ce qu'on refuse de dépenser pour de l'esthétique. Coût : zéro asset,
   zéro requête, zéro maintenance. */
.board {
  position: relative;
  width: 100%;
  height: 100%;
  background:
    radial-gradient(52rem 22rem at 8% -6%,    rgb(90 170 255 / 0.22), transparent 70%),
    radial-gradient(64rem 34rem at 102% 106%, rgb(30 90 190 / 0.28), transparent 72%),
    radial-gradient(120rem 50rem at 20% -10%, #1a3670 0%, transparent 60%),
    var(--bg);
  color: var(--ink);
  display: grid;
  grid-template-rows: 3.6rem 1fr;
  gap: 0.7rem;
  padding: 0.85rem 1.1rem 1rem;
  padding-inline: max(1.1rem, (100% - var(--content-max)) / 2);
}

/* =========================== EN-TÊTE =====================================
   UNE ligne, quatre éléments, aucune image.

     titre — port de départ — mention légale — état & heure

   Ce qui a disparu : le logotype et sa barre de séparation à gauche, et la
   ligne de sous-titre qui recopiait en prose grise (« Départs depuis
   ANVERS. ») une information déjà portée par une donnée. L'en-tête passe de
   4.7 rem sur deux lignes à 3.6 rem sur une seule ; le rem gagné revient au
   tableau, qui est la raison d'être de l'écran.

   La marque n'est pas perdue : elle descend en bas à droite, dans le bloc de
   suivi, à côté du QR (voir `.track-brand`). */

.header {
  display: grid;
  grid-template-columns: auto auto 1fr auto;
  align-items: center;
  gap: 1.3rem;
  padding-bottom: 0.55rem;
  border-bottom: 0.125rem solid var(--line);
}

.title {
  font-size: 2.15rem;
  font-weight: 800;
  letter-spacing: 0.005em;
  line-height: 1;
  white-space: nowrap;
}
.title em { font-style: normal; color: var(--brand-soft); }

/* Port de chargement : une donnée, donc une pastille — et non une phrase.
   Elle vaut pour les 80 tarifs affichés ; c'est la deuxième chose que le
   client vérifie après avoir trouvé sa ville. */
.origin {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.28rem 0.7rem 0.28rem 0.45rem;
  background: rgb(30 156 215 / 0.12);
  border: 0.0625rem solid rgb(127 208 245 / 0.3);
  border-radius: 2rem;
  font-size: 0.95rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  color: var(--brand-soft);
  white-space: nowrap;
}
.origin[hidden] { display: none; }
.origin img {
  width: 1.35rem;
  height: 1.01rem;
  border-radius: 0.1rem;
  object-fit: cover;
  box-shadow: 0 0 0 0.0625rem rgb(255 255 255 / 0.25);
}

/* Mention légale : elle doit être lisible de près et invisible de loin. Elle
   occupe la respiration entre le titre et l'état, donc elle ne coûte aucune
   hauteur — c'est ce qui a permis de supprimer la ligne de sous-titre. */
.note {
  justify-self: end;
  font-size: 0.92rem;
  font-weight: 500;
  color: var(--ink-4);
  white-space: nowrap;
}

.header-right { display: flex; align-items: center; gap: 0.9rem; }

/* Symbole de marque en en-tête : masqué par défaut, chaque vue décide s'il a
   lieu d'être. Il ne sert que quand la vue perd le bloc de suivi qui porte le
   lockup — voir `styles.css`, mode remarques. */
.header-mark { display: none; width: 2.5rem; height: 2.5rem; }

/* Il n'y a PLUS de QR dans l'en-tête, et c'est une décision, pas un oubli.
   Un QR se scanne jusqu'à environ dix fois son côté. Sur une TV 55", 1 rem ≈
   1 cm : le QR d'en-tête mesurait 2.9 rem, donc ~3 cm, donc ~30 cm de portée
   utile — alors qu'il siège en HAUT d'un écran accroché au-dessus des têtes.
   Il était physiquement inscannable ; il occupait un coin de l'en-tête pour
   une action que personne ne pouvait faire.
   Le seul QR de l'écran est celui du bloc de suivi, en bas à droite : c'est
   le point le plus proche du client debout. Voir `.track .qr`. */

/* --- Indicateur de fraîcheur ---------------------------------------------
   Ne ment jamais. Trois états : à jour / en retard / hors ligne. L'écran
   continue d'afficher les derniers tarifs valides dans tous les cas. */
.freshness {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 0.45rem 1rem;
  background: var(--panel);
  border: 0.0625rem solid var(--line);
  border-radius: var(--radius);
}

.freshness-dot {
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 50%;
  background: var(--v-green);
  box-shadow: 0 0 0 0.25rem rgb(79 217 154 / 0.15);
  animation: pulse 2.4s ease-in-out infinite;
}
@keyframes pulse { 50% { opacity: 0.35; } }

.freshness[data-state="stale"]   .freshness-dot { background: var(--v-amber); box-shadow: 0 0 0 0.25rem rgb(247 184 60 / 0.15); }
.freshness[data-state="offline"] .freshness-dot { background: var(--v-rose);  box-shadow: 0 0 0 0.25rem rgb(251 127 149 / 0.15); animation: none; }
/* Mode local : tarifs en vigueur, pas un flux temps réel. Pastille neutre et
   fixe — ni promesse de fraîcheur, ni alarme. */
.freshness[data-state="static"]  .freshness-dot { background: var(--ink-3);   box-shadow: 0 0 0 0.25rem rgb(255 255 255 / 0.06); animation: none; }

/* `nowrap` obligatoire : dans la vue pleine largeur, l'en-tête loge aussi le
   bloc de suivi. Sans cette contrainte, « TARIFS EN VIGUEUR » passait à la
   ligne et débordait de la bande d'en-tête. */
.freshness-text { display: grid; line-height: 1.25; }
.freshness-text strong { font-size: 0.9rem; font-weight: 700; letter-spacing: 0.08em; white-space: nowrap; }
.freshness-text span   { font-size: 0.9rem; color: var(--ink-3); font-variant-numeric: tabular-nums; white-space: nowrap; }

.clock {
  font-size: 1.9rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  padding-left: 0.9rem;
  border-left: 0.0625rem solid var(--line);
}

/* =================== ÉLÉMENTS DE DONNÉE PARTAGÉS ========================= */

/* Drapeau SVG local : les emoji drapeaux ne disposent d'aucun glyphe sous
   Windows et s'afficheraient « CI », « SN »… Le liseré blanc est nécessaire :
   sans lui, la bande blanche d'un drapeau (Sierra Leone, Nigeria, Gambie) se
   fond dans le fond et la forme du drapeau se casse.

   4:3 EXACT (les SVG sont tous en viewBox 640x480) : `object-fit: cover` ne
   rogne donc rien. Un rapport approché rognait quelques pixels en haut et en
   bas, ce qui ne se voit pas sur une étoile centrée mais déséquilibre les
   bandes horizontales de la Sierra Leone ou de la Gambie.

   Le liseré est passé de 1 à 1.5 px de conception : sur une dalle qui
   sur-échelonne (TV 4K servie en 1920), un contour d'un pixel est le premier
   détail que le rééchantillonnage dissout, et le drapeau perd sa forme avant
   de perdre sa couleur. */
.flag {
  width: 2.5rem;
  height: 1.875rem;
  flex: none;
  border-radius: 0.15rem;
  object-fit: cover;
  box-shadow: 0 0 0 0.09rem rgb(255 255 255 / 0.3);
}

/* Puce de remarque : occupe le blanc résiduel après le nom de ville, donc
   coûte zéro colonne. Voir docs/DESIGN-DECISIONS.md §3. */
.chip {
  flex: none;
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  padding: 0.14rem 0.36rem;
  border-radius: 0.28rem;
  color: #04101a;
  background: var(--tag-info);
}
.chip[data-type="promo"]   { background: var(--tag-promo); }
.chip[data-type="warning"] { background: var(--tag-warning); }
.chip[data-type="new"]     { background: var(--tag-new); }

.price {
  text-align: center;
  font-size: 1.82rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
  color: var(--ink);
}
/* Le « € » est JAUNE, et c'est la seule chose jaune de la grille. C'est la
   convention de l'écran SOCAR en production : elle attache l'unité au montant
   sans lui disputer le contraste, puisque le jaune reste plus petit et moins
   lumineux que le blanc pur du chiffre. Le client lit « 1 615 », puis « € ».
   Voir `--gold` : plus aucune alerte n'a le droit d'être jaune. */
.price .cur { font-size: 1.35rem; font-weight: 700; color: var(--gold); margin-left: 0.22rem; }
.price.empty { color: var(--ink-4); font-weight: 500; font-size: 1.3rem; }

/* Le montant occupe une LARGEUR FIXE et s'y cale à droite.
   `4.4ch` = quatre chiffres plus l'espace fine des milliers, mesuré sur les
   chiffres tabulaires d'Inter (`ch` vaut exactement la chasse d'un chiffre).
   Un montant à cinq chiffres pousserait la boîte sans rien casser.

   C'est ce qui met les unités des vingt lignes sur la même verticale, donc
   les « € » aussi. Sans cette réserve, chaque montant était centré sur
   lui-même : « 820 » se décalait d'un demi-chiffre par rapport à « 1 230 »
   juste au-dessus, et la colonne se lisait comme une liste en escalier. Le
   bloc, lui, reste centré dans sa cellule — la colonne garde son axe sous
   l'icône de gabarit de l'en-tête. */
.price .num {
  min-width: 4.4ch;
  text-align: right;
}

/* --- Gabarit véhicule ----------------------------------------------------
   Icônes au trait (famille Lucide), mises à l'échelle sur une ligne de sol
   commune : voiture < van < big van. La progression se lit en balayant
   l'en-tête, avant même les mots.

   Le trait fait ~4 px de rendu à cette taille sur une dalle 1080p, ce qui est
   le seuil en dessous duquel un contour disparaît à 3 m. C'est la raison de
   ces dimensions : plus petit, il faudrait revenir à des aplats. */
.cat {
  width: 2.5rem;
  height: 2.06rem;
  color: #9ec9e4;
  fill: none;
}

/* En-tête d'une colonne tarifaire. `.cat-name` porte le gabarit, `.cat-port`
   le port de chargement — c'est lui qui sépare les deux colonnes BIG VAN. */
.cat-name {
  font-size: 1.12rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  color: #eaf4fb;
  line-height: 1;
}

.cat-port {
  margin-top: 0.26rem;
  padding: 0.08rem 0.4rem;
  background: rgb(30 156 215 / 0.16);
  border: 0.0625rem solid rgb(127 208 245 / 0.32);
  border-radius: 0.3rem;
  font-size: 0.76rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  line-height: 1.35;
  color: var(--brand-soft);
}

/* --- Suffixe de service (« TRANSIT », « TRANSIT MALI ») -------------------
   Contour, pas aplat : les aplats appartiennent aux pastilles de remarque,
   qui signalent une urgence commerciale. Celle-ci qualifie le régime d'une
   ligne — elle doit être vue, pas criée. Voir `cityMarkup`. */
/* Or, comme sur l'écran de production SOCAR, où « TRANSIT » est le seul mot
   coloré de la ligne. Il n'y a aucune ambiguïté avec le « € » : celui-ci est
   collé à un chiffre, celui-là est un mot posé après un nom de ville. */
.via {
  display: inline-block;
  margin-left: 0.55rem;
  padding: 0.1rem 0.42rem;
  vertical-align: 0.22em;
  background: rgb(255 238 74 / 0.1);
  border: 0.0625rem solid rgb(255 238 74 / 0.4);
  border-radius: 0.3rem;
  font-size: 0.86rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  line-height: 1.2;
  color: var(--gold);
}

/* Code de voyage Grimaldi : la référence que le client cite au comptoir. */
.voyage {
  font-size: 0.86rem; font-weight: 800; letter-spacing: 0.08em;
  /* Zéro barré ICI et nulle part ailleurs : `GGA0426` mélange lettres et
     chiffres, c'est le seul endroit de l'écran où un 0 peut être lu O. Sur un
     prix, le zéro barré ferait technique sans rien désambiguïser. */
  color: var(--vc); font-variant-numeric: tabular-nums slashed-zero;
  padding: 0.08rem 0.35rem; border: 0.0625rem solid currentColor;
  border-radius: 0.25rem; opacity: 0.9;
}

.dot {
  width: 0.6rem;
  height: 0.6rem;
  flex: none;
  border-radius: 50%;
  background: var(--vc);
  box-shadow: 0 0 0.45rem rgb(var(--vc-rgb) / 0.75);
}

/* ==================== CARTE DESTINATION (vues « cartes ») ================
   Les deux variantes cartes — avec rail (cartes.html) et pleine largeur
   (cartes-plein.html) — partagent le MÊME objet visuel. Il vit ici ; chaque
   vue ne règle plus que ses tailles, par deux variables :

     --pcell-value   corps du montant
     --pcell-label   corps du libellé de gabarit

   Anatomie, de haut en bas :
     filet navire → bandeau destination → tuiles de prix → remarque → pied.

   Le bandeau est PLEINE LARGEUR (marges négatives). C'est ce qui donne à la
   carte sa silhouette reconnaissable à 3 m : un bloc titre sombre, une zone
   de prix claire. Sans lui, vingt cartes forment un damier uniforme où l'œil
   ne trouve aucune prise et doit lire chaque nom pour se repérer. */

.dcard {
  --pcell-value: 1.55rem;
  --pcell-label: 0.62rem;

  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: grid;
  /* `minmax(0, 1fr)` et non `1fr` : une carte qui porte une remarque a QUATRE
     rangées au lieu de trois. Sans le `minmax`, la rangée des prix refuse de
     descendre sous la hauteur intrinsèque de ses tuiles, le pied est poussé
     hors de la carte et `overflow: hidden` le coupe. Avec, ce sont les tuiles
     qui se resserrent de quelques pixels — invisible, et rien ne disparaît. */
  grid-template-rows: auto minmax(0, 1fr) auto;
  gap: 0.26rem;
  min-height: 0;
  min-width: 0;
  padding: 0 0.5rem 0.4rem;
  background:
    linear-gradient(180deg, rgb(var(--vc-rgb) / 0.14), transparent 40%),
    linear-gradient(180deg, var(--panel-2), var(--panel) 60%);
  /* Le liseré porte la couleur du navire à faible opacité : la carte est
     rattachée à sa rotation sur tout son contour, pas seulement en haut. */
  border: 0.0625rem solid rgb(var(--vc-rgb) / 0.3);
  border-radius: 0.8rem;
  /* Ombre portée + filet de lumière interne : c'est ce qui décolle la carte
     du fond au lieu de la poser dessus. Sur une dalle 55" vue à 3 m, une
     carte plate se confond avec le panneau ; une carte qui projette une ombre
     se compte du premier coup d'œil. Coût GPU nul, l'écran est statique. */
  box-shadow:
    0 0.4rem 1rem rgb(0 0 0 / 0.45),
    inset 0 0.0625rem 0 rgb(255 255 255 / 0.06);
}

/* Filet de couleur navire, en haut de carte. Dessiné en `::before` et non en
   `border-top` : le dégradé et le halo ne sont pas exprimables sur une
   bordure, et c'est ce halo qui rend la couleur lisible à 3 m sur 0.22 rem
   de haut. */
.dcard::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 0.22rem;
  background: linear-gradient(90deg, var(--vc), rgb(var(--vc-rgb) / 0.4));
  box-shadow: 0 0 0.85rem rgb(var(--vc-rgb) / 0.55);
}

/* --- Bandeau destination ------------------------------------------------- */
.dhead {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
  margin: 0 -0.5rem;                    /* pleine largeur de carte */
  padding: 0.5rem 0.6rem 0.38rem;
  background: linear-gradient(180deg, rgb(255 255 255 / 0.085), rgb(255 255 255 / 0.012));
  border-bottom: 0.0625rem solid rgb(var(--vc-rgb) / 0.26);
}
.dflag { width: 2.15rem; height: 1.61rem; border-radius: 0.2rem; }

.dcity {
  flex: 1;
  font-size: 1.5rem;
  font-weight: 800;
  letter-spacing: 0.015em;
  line-height: 1.05;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-shadow: 0 0.0625rem 0.2rem rgb(2 10 18 / 0.6);
}
/* Même pastille, au corps de la carte. */
.dcity .via {
  margin-left: 0.4rem;
  padding: 0.06rem 0.3rem;
  font-size: 0.68rem;
  letter-spacing: 0.06em;
}

/* --- Tuiles de prix ------------------------------------------------------
   Quatre colonnes internes fixes : deux cartes voisines restent comparables
   colonne par colonne, sinon la grille n'est qu'un mur de nombres. */
.dprices {
  display: grid;
  grid-template-columns: repeat(var(--price-cols, 4), 1fr);
  gap: 0.3rem;
  min-width: 0;
  min-height: 0;
}

/* Chaque prix occupe une tuile visible. Sans elle, « 1 380 » et « 1 490 »
   côte à côte se lisent « 1 3801 490 » : le séparateur de milliers français
   est une espace fine. La tuile rend la séparation structurelle au lieu de
   typographique — et le dégradé du haut vers le bas lui donne l'épaisseur
   d'un objet posé, pas d'un rectangle gris. */
.pcell {
  display: grid;
  grid-template-rows: auto auto;
  align-content: center;
  justify-items: center;
  gap: 0.34rem;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  padding: 0.28rem 0.1rem 0.3rem;
  background: linear-gradient(180deg, rgb(255 255 255 / 0.09), rgb(255 255 255 / 0.028));
  border-radius: 0.45rem;
  /* Contour dessiné en ombre interne et non en `border` : une bordure de 1 px
     de chaque côté, c'est 2 px de moins pour le montant, soit 0.04 rem de
     corps perdu sur les quatre colonnes. L'ombre ne consomme aucune largeur
     de mise en page et donne le même liseré. */
  box-shadow:
    inset 0 0 0 0.0625rem rgb(255 255 255 / 0.07),
    inset 0 0.0625rem 0 rgb(255 255 255 / 0.08);
}

.pcell-head { display: grid; justify-items: center; gap: 0.08rem; }

/* Silhouette de gabarit : elle encode la taille du véhicule par sa largeur
   croissante, donc elle se lit avant les mots. Elle ne distingue en revanche
   pas les deux colonnes BIG VAN — c'est le libellé, en dessous, qui le fait. */
.pcell-ico {
  width: 1.55rem;
  height: 1.25rem;
  margin: 0;
  color: #7ba7c4;
}

/* Deux lignes réservées quoi qu'il arrive : c'est ce qui garde les quatre
   montants d'une carte sur la même ligne de base. Voir `priceCell`. */
.pcell-label {
  display: grid;
  align-content: start;
  min-height: 2.16em;
  font-size: var(--pcell-label);
  font-weight: 800;
  letter-spacing: 0.05em;
  line-height: 1.08;
  color: #9dbdd4;
  text-align: center;
  white-space: nowrap;
}
.pcell-label b { font-weight: 700; color: #7098b4; }

.pcell-value {
  display: flex;
  align-items: baseline;
  gap: 0.05em;
  font-size: var(--pcell-value);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.005em;
  line-height: 1;
  color: var(--ink);
}
/* Le symbole reste subordonné au nombre : plus petit, en or SOCAR, jamais en
   blanc pur — sinon il entre en concurrence de contraste avec le chiffre, qui
   est la seule chose que le client est venu lire. Même or que dans la vue
   tableau : une unité ne change pas de couleur d'une vue à l'autre. */
.pcell-cur {
  font-style: normal;
  font-size: 0.62em;
  font-weight: 700;
  color: var(--gold);
}
.pcell-value.empty { color: var(--ink-4); font-weight: 500; font-size: calc(var(--pcell-value) * 0.72); }

/* --- Pied de carte ------------------------------------------------------- */
.dfoot {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.95rem;
  font-weight: 600;
  min-width: 0;
  padding-top: 0.28rem;
  border-top: 0.0625rem solid var(--line-soft);
}
/* Le mot « DÉPART » coûte 2.5 rem et supprime une ambiguïté réelle : sans
   lui, « Great Lagos 15/08 » peut se lire comme une escale, une arrivée ou un
   délai. Le rail droit porte le titre PROCHAINS DÉPARTS, mais la vue pleine
   largeur n'a pas de rail — la carte doit se suffire. */
.dfoot-label {
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.11em;
  color: var(--ink-4);
}
.dfoot-vessel {
  color: var(--vc);
  font-weight: 700;
  flex: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.dfoot-date { color: var(--ink-2); font-weight: 700; font-variant-numeric: tabular-nums; }
.dfoot-tbc { color: var(--v-amber); font-weight: 800; letter-spacing: 0.05em; font-size: 0.85rem; }

/* --- Remarque ------------------------------------------------------------
   L'avantage structurel des vues cartes : la remarque tient dans la carte de
   SA destination, sans ouvrir de colonne et sans faire disparaître le rail.
   La vue tableau, elle, doit choisir entre les deux. */
.dremark {
  font-size: 0.92rem;
  font-weight: 700;
  line-height: 1.15;
  color: #d6e6f1;
  padding: 0.18rem 0.45rem;
  border-radius: 0.32rem;
  background: rgb(255 255 255 / 0.05);
  box-shadow: inset 0.2rem 0 0 var(--tag-info);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.dremark[data-type="promo"]   { box-shadow: inset 0.2rem 0 0 var(--tag-promo); }
.dremark[data-type="warning"] { box-shadow: inset 0.2rem 0 0 var(--tag-warning); }
.dremark[data-type="new"]     { box-shadow: inset 0.2rem 0 0 var(--tag-new); }

/* --------------------------- RAIL DROIT --------------------------------- */

/* Le rail ne contient qu'une scène visible à la fois : elle occupe toute la
   hauteur, sans réserver de piste pour une scène masquée. */
.rail {
  display: grid;
  grid-template-rows: 1fr auto;
  gap: 0.75rem;
  min-height: 0;
}
.rail[hidden] { display: none; }

/* UNE seule cellule, et les deux scènes dedans (voir `.scene`). C'est ce qui
   fige la hauteur du rail : elle ne dépend plus de la scène affichée, donc le
   tableau — qui partage la même rangée — ne se réajuste plus au moment du
   basculement. */
.rail-scenes {
  display: grid;
  grid-template: 1fr / 1fr;
  min-height: 0;
}

.rail-title {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 1.25rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  color: #cfe3f0;
  padding: 0 0.2rem;
}
.rail-title .ico { width: 1.5rem; height: 1.5rem; color: var(--brand); }

/* Cinq cartes DESTINATION, pistes égales : elles portent toutes la même chose
   (une ville, une date, un navire), elles doivent donc avoir la même surface.
   L'ancien rail dimensionnait ses cartes par leur contenu — nécessaire quand
   elles portaient des listes de ports de longueur variable, inutile depuis
   qu'elles n'en portent plus. `minmax(0, 1fr)` autorise la piste à descendre
   sous la hauteur intrinsèque de son contenu : sans lui, un nom de ville long
   ferait déborder le rail au lieu de se resserrer. */
.vessels {
  display: grid;
  grid-auto-rows: minmax(0, 1fr);
  gap: 0.6rem;
  min-height: 0;
}

/* --- Carte destination ---------------------------------------------------
   Anatomie, de haut en bas : drapeau + ville → délai + date → navire.
   Trois lignes, une seule question par ligne. La ville d'abord parce que
   c'est le seul mot que le client cherche des yeux ; le navire en dernier
   parce qu'il ne l'intéresse qu'une fois sa ville trouvée.

   La photographie occupe la moitié droite et se dissout vers la gauche. Elle
   n'apporte AUCUNE information — c'est une image générique tirée des vidéos
   SOCAR, pas le navire nommé — et elle est donc traitée comme telle :
   désaturée, assombrie, teintée de la couleur du navire. Elle ne consomme
   aucune hauteur, ne peut pas manquer, et si le fichier disparaît la carte
   redevient une carte plate parfaitement lisible. */
.dcard-dep {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: grid;
  grid-template-rows: auto auto auto;
  align-content: center;
  gap: 0.35rem;
  padding: 0.7rem 0.95rem;
  background:
    linear-gradient(100deg, rgb(var(--vc-rgb) / 0.14), transparent 62%),
    var(--panel);
  border: 0.0625rem solid rgb(var(--vc-rgb) / 0.42);
  border-left: 0.3rem solid var(--vc);
  border-radius: var(--radius);
  min-height: 0;
  box-shadow: 0 0.35rem 0.9rem rgb(0 0 0 / 0.35);
}

.dep-photo {
  position: absolute;
  inset: 0 0 0 18%;
  z-index: -1;
  background-size: cover;
  background-position: center;
  filter: saturate(0.6) brightness(0.6) contrast(1.05);
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 55%);
          mask-image: linear-gradient(90deg, transparent 0%, #000 55%);
}
/* Voile teinté par-dessus la photo : c'est lui qui rattache l'image à la
   couleur du navire, donc au code couleur du tableau. Sans lui, cinq cartes
   empilées n'ont plus de code couleur lisible à 3 m. */
.dep-photo::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 30%, rgb(var(--vc-rgb) / 0.3));
}

/* Signature de marque, posée sur la photo. Discrète (opacité 0.5) et petite :
   elle marque la carte comme un objet SOCAR sans disputer la lecture à la
   ville, qui est la raison d'être de la carte. */
.dep-mark {
  position: absolute;
  top: 0.55rem;
  right: 0.7rem;
  width: 2.8rem;
  height: 2.8rem;
  opacity: 0.55;
  filter: drop-shadow(0 0.0625rem 0.25rem rgb(2 10 18 / 0.9));
}

.dep-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  min-width: 0;
}
.dep-flag { width: 2.2rem; height: 1.65rem; }

.dep-city {
  font-size: 1.62rem;
  font-weight: 800;
  letter-spacing: 0.015em;
  line-height: 1.05;
  color: var(--ink);
  text-shadow: 0 0.0625rem 0.3rem rgb(2 10 18 / 0.9);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Suffixe de régime au corps de la carte : même pastille que dans le tableau,
   d'un cran plus petite. */
.dep-city .via { margin-left: 0.4rem; padding: 0.06rem 0.32rem; font-size: 0.7rem; }

.dep-when { display: flex; align-items: baseline; gap: 0.6rem; }

/* Le compte à rebours domine la date : « dans 9 jours » est ce que le client
   cherche réellement, la date civile n'est qu'une confirmation. */
.countdown {
  font-size: 1.85rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  line-height: 1;
  text-shadow: 0 0.0625rem 0.3rem rgb(2 10 18 / 0.85);
}
.dep-date { font-size: 1.2rem; font-weight: 700; color: var(--ink-2); font-variant-numeric: tabular-nums; }

/* Le navire vient APRÈS la date, et dans la couleur qui le relie à sa ligne
   du tableau. C'est le seul lien pré-attentif entre les deux moitiés de
   l'écran ; il ne survivrait pas à une mise en gris. */
.dep-ship {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  min-width: 0;
  font-size: 1.05rem;
  font-weight: 700;
}
.dep-ship-name {
  color: var(--vc);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-shadow: 0 0.0625rem 0.25rem rgb(2 10 18 / 0.8);
}
.dep-ship .voyage { flex: none; font-size: 0.78rem; }

/* Repli : aucun départ à venir dans la donnée. Le rail dit pourquoi il est
   presque vide au lieu de laisser un demi-écran blanc. */
.dcard-empty {
  --vc: var(--ink-3);
  --vc-rgb: 143 174 196;
  align-content: center;
  gap: 0.4rem;
}
.dcard-empty .dep-city { font-size: 1.25rem; color: var(--ink-2); white-space: normal; }
.dcard-empty .dep-ship { color: var(--ink-3); font-weight: 500; }

/* --- Scènes du rail ------------------------------------------------------
   Le rail alterne entre les départs et une séquence vidéo. La GRILLE
   TARIFAIRE, elle, ne quitte jamais l'écran : un client qui cherche sa
   destination n'attend jamais. Voir docs/DESIGN-DECISIONS.md §12. */
/* Les deux scènes sont SUPERPOSÉES dans la même cellule. Elles peuvent donc
   être visibles en même temps le temps d'un fondu enchaîné — l'ancienne
   version en remplaçait une par l'autre d'un seul coup, ce qui produisait une
   coupure sèche au milieu du rail toutes les 50 secondes.

   Le fondu ne fait varier que deux propriétés, `opacity` et `transform` :
   toutes deux sont composées par le GPU, sans recalcul de mise en page. Rien
   ne bouge dans le tableau pendant la transition, et le coût reste tenable
   sur le navigateur d'une TV. */
.scene {
  grid-area: 1 / 1;
  display: grid;
  grid-template-rows: auto 1fr;
  gap: 0.75rem;
  min-height: 0;
  opacity: 1;
  transform: none;
  transition:
    opacity   var(--scene-fade) cubic-bezier(0.4, 0, 0.2, 1),
    transform var(--scene-fade) cubic-bezier(0.4, 0, 0.2, 1);
}
.scene[hidden] { display: none; }

/* Entrante : elle monte depuis le bas. Sortante : elle continue vers le haut.
   Les deux vont dans le MÊME sens — c'est ce qui fait lire un glissement
   plutôt qu'un croisement, et le déplacement (0.9rem) reste assez court pour
   que l'œil suive sans jamais voir deux titres se superposer franchement. */
.scene-enter { opacity: 0; transform: translateY(0.9rem) scale(0.995); }
.scene-leave { opacity: 0; transform: translateY(-0.9rem) scale(0.995); }

.scene-media { grid-template-rows: auto 1fr auto; }

/* Les vidéos sources sont en 854x480. Cadrées dans le rail (440 px de large)
   elles restent au-dessus de leur résolution native, donc nettes. En plein
   écran sur une 55" elles seraient agrandies 2,25x et visiblement floues. */
.scene-media video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--radius);
  border: 0.0625rem solid var(--line);
  background: #000;
  min-height: 0;
}
.media-caption {
  font-size: 1rem;
  font-weight: 600;
  color: var(--ink-3);
  text-align: center;
}

/* --- Suivi du véhicule ---------------------------------------------------
   Seul bloc commercial de l'écran, parce que c'est le seul qui porte une
   action utile depuis un hall d'attente. Les statistiques « +15 000 véhicules
   par mois » ont été retirées : invérifiables, donc coûteuses en crédibilité
   pour zéro service rendu au client. */
.track {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 0.9rem;
  padding: 0.7rem 0.7rem 0.7rem 1rem;
  background: linear-gradient(135deg, #12557d, #0a2a3f);
  border: 0.0625rem solid #2a7ba8;
  border-radius: var(--radius);
}
.track-text { display: grid; gap: 0.12rem; }

/* Lockup de marque, position de signature. Le wordmark fourni est en encre
   foncée sur fond clair et devient délavé une fois inversé : on ne garde que
   le symbole (déjà au bleu de marque, fond transparent) et on compose le nom.
   Un filet le sépare de l'appel à l'action, qui reste le sujet du bloc. */
.track-brand {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding-bottom: 0.4rem;
  margin-bottom: 0.35rem;
  border-bottom: 0.0625rem solid rgb(127 208 245 / 0.22);
}
.track-brand img { width: 1.9rem; height: 1.9rem; flex: none; }
.track-brand b {
  display: grid;
  font-size: 1.12rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  line-height: 1.05;
}
.track-brand i {
  font-style: normal;
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.19em;
  color: var(--brand-soft);
}
/* Variante compacte (cartes pleine largeur) : le bloc vit dans l'en-tête, où
   il n'a que 3.6 rem — la marque y serait redondante avec le titre voisin. */
.track-compact .track-brand { display: none; }

.track-text h3 { font-size: 1.3rem; font-weight: 800; letter-spacing: 0.04em; line-height: 1.1; }
.track-text p { font-size: 0.9rem; color: #b9dcf0; line-height: 1.25; }
.track-text .url { font-size: 1.05rem; font-weight: 700; color: var(--brand-soft); letter-spacing: 0.03em; }
/* Portée de scan ≈ 10 × le côté du symbole. Sur une TV 55", 1 rem ≈ 1 cm :
   à 5.2 rem le client devait coller son téléphone à l'écran. À 7.2 rem la
   portée passe à ~70 cm, soit un bras tendu — la seule distance à laquelle
   quelqu'un scanne réellement quelque chose dans un hall. */
.track .qr {
  width: 7.2rem;
  height: 7.2rem;
  background: #fff;
  border-radius: 0.35rem;
  padding: 0.18rem;
}
.track .qr svg { width: 100%; height: 100%; display: block; }

/* --- Veille (hors heures d'ouverture) ------------------------------------
   Plein écran, au-dessus de tout : c'est un ÉTAT de l'écran, pas un bloc de
   la mise en page. Il couvre aussi les bandes noires du letterbox, sinon la
   coupure serait visible depuis l'autre bout du hall.

   Les sources disponibles sont en 480p/720p : agrandies à 1920 elles sont
   molles. C'est assumé ici, et seulement ici — à cette heure-là personne ne
   les regarde de près, et ce qu'on cherche est ailleurs (faire bouger la
   dalle, laisser une marque à qui passe devant la porte). Le voile ci-dessous
   masque l'essentiel du défaut. */
.standby {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: #000;
  display: grid;
  place-items: center;
}
.standby[hidden] { display: none; }

.standby-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Double rôle : il rattrape le manque de définition ET garantit le contraste
   de la plaque de marque quel que soit le plan qui passe derrière. Le voile
   est calibré sur le plan le PLUS CLAIR de la boucle (le quai en plein jour),
   pas sur le plus sombre : c'est le seul réglage qui tienne pour les trois. */
.standby::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(75% 60% at 50% 50%,
                              rgb(2 10 18 / 0.55), rgb(2 10 18 / 0.9));
}

/* Le voile seul ne suffit pas : au centre exact du cadre passent des coques
   claires et des conteneurs blancs. La plaque porte donc son propre fond —
   même panneau sombre que le reste de l'écran, donc même famille visuelle. */
.standby-plate {
  position: relative;
  z-index: 1;
  display: grid;
  justify-items: center;
  gap: 0.7rem;
  text-align: center;
  padding: 2.6rem 4.4rem 2.2rem;
  background: rgb(4 14 24 / 0.86);
  border: 0.0625rem solid rgb(127 208 245 / 0.22);
  border-radius: var(--radius);
  box-shadow: 0 1.5rem 4rem rgb(0 0 0 / 0.55);
}
.standby-plate img { width: 6.5rem; height: 6.5rem; }
.standby-plate b {
  display: grid;
  font-size: 3.2rem;
  font-weight: 800;
  letter-spacing: 0.16em;
  line-height: 1.05;
  color: var(--ink);
}
.standby-plate i {
  font-style: normal;
  font-size: 1.15rem;
  font-weight: 600;
  letter-spacing: 0.24em;
  color: var(--brand-soft);
}
.standby-plate p {
  margin-top: 0.5rem;
  padding-top: 0.9rem;
  border-top: 0.0625rem solid rgb(127 208 245 / 0.18);
  font-size: 1.2rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--ink-2);
}

/* --- Pied (vues sans rail droit) -----------------------------------------
   Quand la grille tarifaire occupe toute la largeur, le bloc de suivi n'a
   plus de rail où vivre. Il descend en pied de page, où il reste affiché en
   permanence : c'est la seule action possible depuis un hall d'attente. */
.footer {
  display: grid;
  grid-template-columns: 1fr 34rem;
  align-items: center;
  gap: 2rem;
  padding-top: 0.6rem;
  border-top: 0.125rem solid var(--line);
}

.foot-brand { display: flex; align-items: center; gap: 0.9rem; }
.foot-brand img { width: 2.8rem; height: 2.8rem; }
.foot-brand div { display: grid; line-height: 1.25; }
.foot-brand strong { font-size: 1.45rem; font-weight: 800; letter-spacing: 0.05em; }
.foot-brand strong span { color: var(--brand-soft); font-weight: 500; letter-spacing: 0.09em; }
.foot-brand div > span { font-size: 0.95rem; color: var(--ink-3); }

/* =========================== ÉTAT FATAL ================================== */

.fatal {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 0.9rem;
  background: var(--bg);
  z-index: 10;
}
.fatal[hidden] { display: none; }
.fatal img { width: 7rem; opacity: 0.5; }
.fatal p    { font-size: 2.6rem; font-weight: 800; }
.fatal span { font-size: 1.4rem; color: var(--ink-3); }
