/*
 * Feuille de style des fiches publiques de cols.
 *
 * Servie par le backend, pas par nginx : ces pages ne passent pas par le conteneur
 * frontend. Les valeurs sont donc RECOPIÉES et non importées — le thème de la SPA vit dans
 * son bundle, et l'importer ici ferait dépendre une page publique du déploiement d'un
 * autre conteneur.
 *
 * Recopiées, mais plus réinventées. Cette section était la seule surface du produit à
 * porter sa propre palette : un bleu d'accent à elle (#2D6A8F contre le #0f4c81 de la
 * marque), une encre à elle, et des cartes à bord net sans ombre là où la landing et
 * l'application dessinent une carte à coin large et ombre douce. Ce n'était visible que
 * dans l'enchaînement — un lecteur qui arrive de `/` ou qui clique « Rejoindre la bêta »
 * traverse deux fois la frontière et voit deux produits. Les valeurs ci-dessous sont donc
 * celles de `frontend/src/styles/globals.css` (thème clair) et de `landing/styles.css`,
 * au caractère près, sous LEURS noms : ce qui doit rester comparable doit d'abord être
 * nommable.
 *
 * Où les deux sources divergent, c'est l'application qui fait foi — ses `--text-secondary`
 * / `--text-muted` sont assombris par rapport à la landing avec une raison écrite à côté
 * (contraste WCAG AA sur `--bg-base` ET `--bg-elevated`), et une raison écrite bat une
 * valeur héritée.
 *
 * Pas de bascule clair/sombre : la page est servie à un lecteur de passage et à un robot,
 * et un thème persisté suppose une session que ces pages n'ont pas. `color-scheme: light`
 * est explicite pour que le navigateur ne tente pas d'inverser les couleurs lui-même —
 * c'est aussi ce qui autorise à ne recopier que la moitié claire des tokens.
 */

:root {
  color-scheme: light;
  /*
   * Deux tailles de texte secondaire, et pas sept. La feuille en portait 0,75 / 0,8125 /
   * 0,875 / 0,9375 rem pour dire la même chose — une légende, une note de méthode, un
   * libellé — donc quatre nuances qu'aucune règle ne distinguait et qu'un lecteur ne peut
   * pas lire comme une hiérarchie. Les nommer est ce qui empêche la cinquième.
   */
  --text-xs: 0.8125rem;
  --text-sm: 0.875rem;

  /* Palette de marque — Glacier / Nordic (globals.css, landing/styles.css). */
  --glacier-900: #0a2e4d;
  --glacier-800: #0f4c81;
  --glacier-600: #1976d2;
  --glacier-50: #f0f7ff;
  --nordic-500: #00bfa5;

  /* Échelle neutre « ice », dont les tokens sémantiques ci-dessous sont tirés. */
  --ice-900: #1a1f2e;
  --ice-400: #cbd5e0;
  --ice-300: #e2e8f0;
  --ice-100: #f7fafc;

  /* Tokens sémantiques (mêmes noms, mêmes valeurs que l'application en thème clair). */
  --bg-base: #ffffff;
  --bg-elevated: var(--ice-100);
  --bg-overlay: #ffffff;
  --text-primary: var(--ice-900);
  --text-secondary: #5b6779;
  --text-muted: #64748b;
  --border-default: var(--ice-400);
  --border-light: var(--ice-300);

  /*
   * Ombres et rayons : c'est ce qui fait la différence entre « une carte » et « un cadre ».
   * Les ombres sont teintées de glacier plutôt que noires — la marque les définit ainsi, et
   * une ombre noire sur un fond bleuté grise la surface qu'elle porte.
   */
  --shadow-sm: 0 1px 2px rgba(15, 76, 129, 0.05);
  --shadow-md: 0 4px 12px rgba(15, 76, 129, 0.08);
  --shadow-lg: 0 8px 24px rgba(15, 76, 129, 0.12);
  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 0.875rem;

  /*
   * Alias courts, conservés à dessein. Les 875 lignes qui suivent les lisent, donc les
   * retirer serait une réécriture mécanique du fichier entier pour renommer ce qui est déjà
   * juste — et le nom court dit la même chose. Ce qui compte est qu'ils n'aient plus de
   * valeur propre : chacun POINTE sur le token partagé, donc rien ici ne peut plus dériver
   * de la marque sans qu'on le voie dans ce bloc.
   */
  --base: var(--bg-elevated);
  --surface: var(--bg-overlay);
  --ink: var(--text-primary);
  --ink-soft: var(--text-secondary);
  --ink-muted: var(--text-muted);
  --edge: var(--border-default);
  --edge-soft: var(--border-light);
  --accent: var(--glacier-800);
  --accent-hover: var(--glacier-900);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--base);
  color: var(--ink);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  line-height: 1.6;
}

a { color: var(--accent); }

.site-head,
.site-foot {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 1.5rem;
  border-bottom: 1px solid var(--edge);
}

.site-foot {
  border-bottom: none;
  border-top: 1px solid var(--edge);
  color: var(--ink-muted);
  font-size: var(--text-sm);
}

.site-foot p { margin: 0; }

.wordmark {
  font-weight: 700;
  font-size: 1.25rem;
  color: var(--ink);
  text-decoration: none;
}

.page {
  max-width: 48rem;
  margin: 0 auto;
  padding: 2rem 1.5rem 3rem;
}

/*
 * ---------------------------------------------------------------------------
 * Débordement des blocs denses
 *
 * La colonne de texte fait 48rem, ce qui est la bonne mesure pour de la prose et la
 * mauvaise pour une carte et un tableau de huit colonnes : sur un écran de 1 280 px, cinq
 * colonnes du tableau de l'index (distance, D+, pente, difficulté, versants) tombaient hors
 * cadre pendant que 500 px restaient vides de chaque côté. Le titre annonce « du plus dur au
 * plus facile » et la colonne qui justifie l'ordre était celle qu'on ne voyait pas.
 *
 * D'où un élargissement des seuls blocs qui en ont besoin — carte, tableau, profil — par
 * marges négatives, et jamais de la page entière : la prose reste à sa mesure. `100%` est ici
 * la largeur du bloc parent (la colonne de texte), donc la marge vaut zéro tant que la
 * fenêtre est plus étroite que la cible et ne peut pas provoquer de défilement horizontal.
 */
@media (min-width: 64rem) {
  .bleed {
    margin-inline: calc(-1 * (min(68rem, 100vw - 3rem) - 100%) / 2);
  }
}

/*
 * Lien d'évitement : /cols porte 134 points de carte, 134 lignes de tableau et 22
 * départements, soit près de 290 tabulations avant d'atteindre quoi que ce soit d'autre.
 * Visible au focus seulement — il ne s'adresse qu'au clavier.
 */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 10;
  padding: 0.625rem 1rem;
  background: var(--surface);
  border: 1px solid var(--edge);
  border-radius: 0 0 var(--radius-md) 0;
}

.skip-link:focus {
  left: 0;
}

/*
 * Le focus était celui du navigateur, alors que tout le reste de la page est dessiné. Sur
 * `:focus-visible` et non `:focus`, pour qu'un clic à la souris ne pose pas d'anneau.
 */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 0.125rem;
}

/*
 * Titre de tableau lu par une technologie d'assistance et par elle seule : les tableaux ont
 * un <h2> au-dessus, mais rien ne relie les deux hors du flux visuel.
 */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

h1 {
  font-size: clamp(1.75rem, 4vw, 2.5rem);
  line-height: 1.2;
  margin: 0 0 1.5rem;
}

h2 {
  font-size: 1.25rem;
  margin: 2.5rem 0 0.75rem;
}

h3 {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 1.05rem;
  margin: 0 0 0.5rem;
}

.lede { color: var(--ink-soft); }

dl {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 0.75rem 1.5rem;
  margin: 0;
}

dl div { margin: 0; }

dt {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-muted);
}

dd {
  margin: 0;
  font-size: 1.125rem;
  font-weight: 600;
}

/*
 * La carte se redimensionne avec la page. `height: auto` + le viewBox du SVG conservent le
 * rapport d'aspect sans hauteur fixe, sinon le tracé s'écrase sur un écran étroit.
 */
.map-frame {
  border: 1px solid var(--edge-soft);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--surface);
  box-shadow: var(--shadow-md);
}

/*
 * Deux dessins de la même carte, un par forme de lecteur, et un seul rendu à la fois.
 *
 * Le cadre paysage est une forme de bureau. Rendu dans les ~350 px d'un téléphone il tient
 * le même terrain dans la moitié des pixels : les toponymes de l'image IGN sortent à deux
 * ou trois pixels et une montée qui remplit le cadre devient un trait. La carte est là et
 * illisible, ce qui est pire qu'absente — sur une capture d'écran elle a l'air correcte.
 *
 * ROGNER EN CSS est ce qu'on essaie d'abord et c'est exactement ce que cette fonctionnalité
 * interdit : le cadre est lu sur la projection pour que « le cadre récupéré soit le cadre
 * dessiné », et un rognage garde le premier en changeant le second, en silence. Le lecteur
 * étroit reçoit donc un dessin réellement différent — sa projection, ses tracés, son échelle
 * et son image stockée à lui (clé `…@m`).
 *
 * Coût assumé : la page porte les deux, puisque rien côté serveur ne dit laquelle sera
 * affichée. Les chemins compressent bien ; une carte que personne ne peut lire sur
 * l'appareil que la plupart des cyclistes tiennent coûte plus cher.
 */
.map-frame--narrow { display: none; }

@media (max-width: 40rem) {
  .map-frame--wide { display: none; }
  .map-frame--narrow { display: block; }
}

.col-map {
  display: block;
  width: 100%;
  height: auto;
}

.legend {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  padding: 0;
  margin: 0.75rem 0 0;
  font-size: var(--text-sm);
}

.legend li {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.swatch {
  display: inline-block;
  width: 0.875rem;
  height: 0.875rem;
  border-radius: 0.25rem;
  flex: none;
}

/*
 * `.attribution` a disparu avec les trois paragraphes qui la portaient (revue UI/UX #27) :
 * chaque source est créditée là où elle apparaît — dans le dessin, dans l'image, et une
 * fois dans le pied de page — jamais répétée en prose à côté.
 */
.footnote,
.method {
  color: var(--ink-muted);
  font-size: var(--text-xs);
}

.ascent {
  background: var(--surface);
  border: 1px solid var(--edge-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: 1rem 1.25rem;
  margin-bottom: 0.75rem;
}

.col-list,
.nearby ul {
  list-style: none;
  padding: 0;
  margin: 0;
}

.col-list li,
.nearby li {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
  align-items: baseline;
  justify-content: space-between;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--edge);
}

.col-list li span,
.nearby li span {
  color: var(--ink-muted);
  font-size: var(--text-sm);
}

.count {
  color: var(--ink-muted);
  font-size: var(--text-sm);
  margin: 0 0 0.5rem;
}

.cta {
  margin-top: 3rem;
  padding: 1.5rem;
  background: var(--surface);
  border: 1px solid var(--edge-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
}

.cta h2 { margin-top: 0; }

/*
 * L'encre d'action est celle de la marque, pas l'encre du texte. Le bouton était peint en
 * `--ink` — donc en couleur de paragraphe — ce qui en faisait le seul appel à l'action du
 * produit à ne pas être bleu glacier, sur la page d'où part une inscription.
 */
.cta-button {
  display: inline-block;
  margin-top: 0.5rem;
  padding: 0.625rem 1.25rem;
  background: var(--accent);
  color: var(--bg-base);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  text-decoration: none;
  font-weight: 600;
}

.cta-button:hover { background: var(--accent-hover); }

/*
 * Sous le bouton : ce que coûte le clic, puis comment les sorties rentrent, puis le second
 * « oui ». Trois lignes distinctes et non un paragraphe — c'est une hiérarchie, et la fondre
 * en un bloc de petite ligne est précisément ce qui faisait disparaître la troisième.
 */
.cta-terms {
  margin-top: 0.75rem;
  margin-bottom: 0;
  font-size: var(--text-sm);
  color: var(--ink-soft);
}

.cta-devices {
  margin-top: 0.5rem;
  margin-bottom: 0;
  font-size: var(--text-sm);
  color: var(--ink-muted);
}

/*
 * Le second appel à l'action : la seule chose qu'un lecteur Garmin ou Coros puisse accorder
 * aujourd'hui, donc il doit se voir et se remplir. Il est SECONDAIRE et le reste : séparé du
 * bouton par un filet plutôt que peint en aplat, parce que créer un compte est le meilleur
 * résultat quand il est possible — et deux aplats côte à côte ne diraient plus lequel répond
 * à quoi. (`.cta-alt` / `.cta-secondary` habitaient ici quand le second « oui » était un lien
 * vers la landing ; le formulaire les a remplacés, et une règle que plus rien ne lit est le
 * reliquat que les autres gardes de ce dépôt échouent à laisser passer.)
 */
.cta-waitlist {
  margin-top: 1.25rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--edge-soft);
}

.cta-waitlist-lead {
  margin: 0 0 0.625rem;
  font-weight: 600;
}

/*
 * Le champ et le bouton sur une ligne quand la place le permet, et l'un sous l'autre sinon :
 * `wrap` plutôt qu'une media query, la bascule étant décidée par la largeur restante et non
 * par celle de l'écran. Le champ prend ce qui reste (`flex: 1 1 16rem`), donc il ne se réduit
 * jamais sous une largeur où l'on ne voit plus ce qu'on tape.
 */
.cta-waitlist-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.cta-waitlist-row input {
  flex: 1 1 16rem;
  min-width: 0;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--edge);
  border-radius: var(--radius-md);
  background: var(--bg-base);
  color: var(--ink);
  font: inherit;
}

.cta-waitlist-row input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.cta-waitlist-row button {
  padding: 0.5rem 1rem;
  border: 1px solid var(--accent);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--accent);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

.cta-waitlist-row button:hover {
  background: var(--accent);
  color: var(--bg-base);
}

/*
 * Le `fieldset` porte la question (« quelle montre ? ») et les deux cases y répondent, donc il
 * garde sa `legend` — sans elle un lecteur d'écran annonce deux cases sans dire de quoi elles
 * parlent. La bordure de `fieldset` est retirée à dessein : c'est un groupe sémantique, pas un
 * encadré, et le filet du bloc au-dessus dit déjà où la section commence.
 */
.cta-waitlist-devices {
  margin: 0.75rem 0 0;
  padding: 0;
  border: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1rem;
  align-items: baseline;
}

.cta-waitlist-devices legend {
  padding: 0;
  font-size: var(--text-sm);
  color: var(--ink-soft);
}

.cta-waitlist-devices label {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  /* Une case à cocher est une cible tactile : le libellé la porte, donc il doit être assez
     haut pour qu'on le vise au pouce. */
  min-height: 2.75rem;
  font-size: var(--text-sm);
  cursor: pointer;
}

.cta-waitlist-devices input {
  accent-color: var(--accent);
}

.cta-waitlist-consent {
  margin: 0.5rem 0 0;
  font-size: var(--text-sm);
  color: var(--ink-muted);
}

/*
 * La réponse à un envoi. Un état complet et non un bandeau d'erreur : c'est la page entière,
 * et le refus (adresse invalide, aucune case, trop de demandes) est un diagnostic fini plutôt
 * qu'une panne — même règle que l'envoi de test d'`/admin/mail`, qui répond 200 sur un refus.
 */
.waitlist-outcome {
  margin: 2rem 0;
  padding: 1.5rem;
  background: var(--surface);
  border: 1px solid var(--edge-soft);
  border-left: 4px solid var(--edge);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
}

.waitlist-outcome h1 { margin-top: 0; }

.waitlist-outcome.is-ok { border-left-color: var(--accent); }

/*
 * Le refus n'est PAS peint en rouge d'erreur : rien n'a cassé, et l'encre d'alerte enverrait
 * chercher une panne là où il n'y a qu'une case à cocher. Le filet reste neutre, le texte dit
 * quoi faire.
 */
.waitlist-outcome.is-ko { border-left-color: var(--ink-muted); }

/*
 * ---------------------------------------------------------------------------
 * Fil d'Ariane
 *
 * Visible, et jumeau du BreadcrumbList du JSON-LD. Ces pages sont atteintes froid depuis un
 * résultat de recherche et jamais par le site, donc c'est le seul endroit où un lecteur
 * apprend que la page appartient à un catalogue.
 */
.breadcrumb {
  font-size: var(--text-sm);
  color: var(--ink-muted);
  margin-bottom: 1rem;
}

.breadcrumb ol {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 0.5rem;
  padding: 0;
  margin: 0;
}

.breadcrumb li + li::before {
  content: "›";
  margin-right: 0.5rem;
  color: var(--edge);
}

.lang-switch { margin-left: 1rem; }

.site-head nav {
  display: flex;
  align-items: baseline;
}

/*
 * ---------------------------------------------------------------------------
 * Tableaux
 *
 * `overflow-x: auto` sur un conteneur dédié, et jamais sur le corps de la page : un tableau
 * de huit colonnes ne rentre pas sur un téléphone, et la seule chose pire qu'un tableau qui
 * défile est une page entière qui défile latéralement.
 */
.table-scroll {
  overflow-x: auto;
  border: 1px solid var(--edge-soft);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  /*
   * Ombres de défilement. Un tableau coupé sans indice se lit comme un tableau complet : la
   * fiche du Galibier montrait trois colonnes sur sept et rien ne disait que les quatre
   * autres existaient. Les deux premiers dégradés sont des caches de la couleur de fond,
   * `local` donc solidaires du contenu ; les deux suivants sont les ombres, `scroll` donc
   * fixes au cadre — ils ne se voient que lorsqu'un cache a défilé sous eux, c'est-à-dire
   * exactement quand il reste quelque chose à voir de ce côté.
   */
  background-image:
    linear-gradient(to right, var(--surface) 30%, rgba(255, 255, 255, 0)),
    linear-gradient(to left, var(--surface) 30%, rgba(255, 255, 255, 0)),
    linear-gradient(to right, rgba(27, 58, 75, 0.14), rgba(27, 58, 75, 0)),
    linear-gradient(to left, rgba(27, 58, 75, 0.14), rgba(27, 58, 75, 0));
  background-position: 0 0, 100% 0, 0 0, 100% 0;
  background-repeat: no-repeat;
  background-size: 2.5rem 100%, 2.5rem 100%, 0.9rem 100%, 0.9rem 100%;
  background-attachment: local, local, scroll, scroll;
}

/*
 * Et une phrase, parce que l'ombre est une nuance et que la conclusion à en tirer ne l'est
 * pas. Elle disparaît dès que la largeur disponible suffit — au-delà, le tableau tient.
 */
.scroll-hint {
  color: var(--ink-muted);
  font-size: var(--text-sm);
  margin: 0.5rem 0 0;
}

@media (min-width: 64rem) {
  .scroll-hint { display: none; }
}

table {
  border-collapse: collapse;
  width: 100%;
  font-size: var(--text-sm);
}

thead th {
  text-align: left;
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-muted);
  font-weight: 600;
  white-space: nowrap;
  padding: 0.625rem 0.75rem;
  border-bottom: 1px solid var(--edge);
}

tbody th,
tbody td {
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--edge);
  white-space: nowrap;
  text-align: left;
  font-weight: 400;
}

/*
 * La colonne du versant principal est la seule dont le contenu est une phrase — « Depuis
 * Saint-Étienne-de-Cuines » — donc la seule qui gagne à passer à la ligne. C'est elle qui
 * faisait déborder le tableau de l'index de 1 206 px dans une page qui en offre 1 088 ;
 * toutes les autres colonnes sont des nombres, et un nombre coupé en deux lignes est pire
 * qu'un tableau qui défile.
 */
.cols-table td.main-ascent,
.ascents-table th[scope='row'] {
  white-space: normal;
  min-width: 9rem;
}

/*
 * Tableaux d'article : le repli est la règle, pas l'exception.
 *
 * Le `nowrap` ci-dessus est écrit pour une fiche de col, dont les tableaux sont des
 * MESURES — une distance, un D+, une pente — et où un nombre coupé en deux lignes est
 * effectivement pire qu'un tableau qui défile. Un tableau du carnet méthodologique est
 * l'inverse : chacun porte une colonne de PROSE (« Ce que ça veut dire », « Lecture »),
 * donc l'interdire de passer à la ligne étale une phrase sur une seule ligne et pousse
 * tout le reste hors de l'écran.
 *
 * Mesuré sur `/articles/pmc-ctl-atl-tsb`, colonne de 310 px (viewport de 360) : les cinq
 * tableaux débordaient de 694, 354, 268, 365 et 131 px — le premier faisait 1 004 px de
 * large, soit plus de trois écrans à parcourir latéralement pour lire une cellule. Après :
 * 62, 70, 0, 25 et 0. Sur les six autres articles, cinq tableaux sur huit débordaient à
 * 360 px et **aucun ne déborde plus**.
 *
 * Et ce n'était pas qu'un défaut mobile : ce premier tableau dépasse la colonne de texte
 * (48 rem) à TOUTE largeur, donc il débordait aussi de 286 px sur un écran de 1 280. Une
 * page en 1 280 est justement celle où l'on croit avoir vérifié le rendu.
 *
 * Le repli est sûr ici parce que la prose de ces articles joint déjà ses grandeurs par des
 * espaces insécables (« 42&nbsp;jours », « 63&nbsp;% », « 1&nbsp;−&nbsp;e<sup>−1/τ</sup> »)
 * : un nombre n'y offre aucune occasion de coupure, donc ce que le repli casse est une
 * phrase et jamais une mesure. Une colonne dont le contenu reste insécable garde sa
 * largeur, et `.table-scroll` reste le filet — ce qu'on retire est le défilement du cas
 * courant, pas la possibilité de défiler.
 *
 * L'échange est assumé et va dans le bon sens : le tableau devient plus haut (le premier
 * passe de 140 à 692 px sur un téléphone). Un tableau haut se lit en faisant défiler la
 * page, ce qu'un lecteur fait déjà ; un tableau large demande un geste latéral à
 * l'intérieur d'un bloc, sur du texte qu'aucun indice ne dit tronqué.
 */
.article-body thead th,
.article-body tbody th,
.article-body tbody td {
  white-space: normal;
}

/*
 * Et le rembourrage rétrécit là où il coûte le plus. À 0,75 rem de chaque côté, quatre
 * colonnes dépensent 96 px des 310 disponibles — près d'un tiers de la largeur en marges
 * intérieures. Mesuré, la moitié du débordement restant : 94 → 62 px sur le premier
 * tableau, 110 → 70 sur le deuxième, 57 → 25 sur le quatrième. Au-delà du seuil la place
 * ne manque pas, et le rembourrage reste celui des autres tableaux.
 */
@media (max-width: 40rem) {
  .article-body thead th,
  .article-body tbody th,
  .article-body tbody td {
    padding-left: 0.5rem;
    padding-right: 0.5rem;
  }
}

tbody tr:last-child th,
tbody tr:last-child td { border-bottom: none; }

tbody th { font-weight: 600; }

.no-figures {
  color: var(--ink-muted);
  font-style: italic;
}

/*
 * Barre de pente d'un kilomètre : dessinée À CÔTÉ de la valeur, dans son propre couloir de
 * largeur fixe.
 *
 * Elle était un `inline-block` posé devant le texte, large d'un pourcentage de SA PROPRE
 * CELLULE — donc à 90 % de pente relative elle occupait presque toute la cellule, et la
 * valeur, que `white-space: nowrap` interdit de replier, débordait sur la colonne suivante.
 * Rendu en production : « 10,8%26 m » au kilomètre 3 de la Madeleine, ni la pente ni
 * l'altitude lisibles, sur cinq lignes de dix-neuf. Le couloir borne désormais la barre, et
 * `flex` garde la valeur à sa droite quoi que la barre fasse.
 */
/*
 * `inline-block` et surtout PAS `display: flex` sur la cellule : une `<td>` qui devient une
 * boîte flex quitte le modèle de tableau, et ses bordures cessent de s'aligner sur celles de
 * la colonne d'à côté — mesuré, la ligne de séparation s'arrêtait au milieu du tableau. Le
 * couloir est donc une boîte en ligne de largeur fixe, ce qui borne la barre sans rien changer
 * à la mise en page du tableau.
 */
.split-grade { white-space: nowrap; }

.split-bar-track {
  display: inline-block;
  width: 7rem;
  height: 0.5rem;
  margin-right: 0.625rem;
  vertical-align: middle;
  border-radius: 0.125rem;
  background: var(--edge);
  overflow: hidden;
}

.split-bar {
  display: block;
  height: 100%;
  min-width: 2px;
  border-radius: 0.125rem;
  background: var(--accent);
}

.split-value {
  font-variant-numeric: tabular-nums;
}

/*
 * La barre prend l'encre de la bande de pente du profil ci-dessus. Deux encodages de la même
 * grandeur sur une même page qui ne se ressemblent pas obligent le lecteur à apprendre deux
 * fois la même chose ; la couleur vient du même jeu que le profil, servie par le gabarit.
 */
@media (max-width: 30rem) {
  .split-bar-track { width: 4rem; }
}

/*
 * ---------------------------------------------------------------------------
 * Profil altimétrique
 *
 * Même traitement que la carte : le viewBox du SVG porte le rapport d'aspect, la largeur
 * suit la page. Une hauteur fixe écraserait le tracé sur un écran étroit.
 */
.profile-frame {
  border: 1px solid var(--edge-soft);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  margin-bottom: 0.5rem;
}

.col-profile,
.col-index-map {
  display: block;
  width: 100%;
  height: auto;
}

.gradient-key { font-size: var(--text-sm); }

.hard-numbers { margin-top: 1rem; }

.riders {
  color: var(--ink-soft);
  font-size: var(--text-sm);
  margin: 0.75rem 0 0;
}

.ascent-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  font-size: var(--text-sm);
  margin: 0.5rem 0 0;
}

.ascent-links .gpx { font-weight: 600; }

.ascent h3 a { text-decoration: none; }

.department h3 {
  font-size: 1rem;
  margin: 1.5rem 0 0.25rem;
}

/*
 * L'ouverture du col : mise en évidence plutôt que rangée dans la liste de définitions.
 * D'avril à juin, c'est la question pour laquelle on ouvre la fiche.
 */
.closure {
  margin: 1rem 0 0;
  padding: 0.625rem 0.875rem;
  background: var(--surface);
  border: 1px solid var(--edge);
  border-left: 4px solid var(--accent);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
}

.closure-label {
  font-weight: 600;
  margin-right: 0.5rem;
}

.surface {
  color: var(--ink-muted);
  font-size: var(--text-sm);
  margin: 0.25rem 0 0;
}

/*
 * Passages de course.
 *
 * Le tableau reprend la mise en forme des autres (.table-scroll le rend défilable sur un
 * écran étroit) ; seules deux choses lui sont propres : l'itinéraire d'étape passe sous le
 * numéro plutôt qu'à côté — « Étape 4 » et « Pinerolo → Valloire » sur une ligne font une
 * colonne deux fois trop large sur mobile — et le <details> qui replie les passages anciens
 * n'a pas le chevron par défaut du navigateur, qui varie trop d'un moteur à l'autre.
 */
.races h2 { margin-top: 2.5rem; }

.races-table th[scope='row'] {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.race-route {
  display: block;
  color: var(--ink-muted);
  font-size: var(--text-sm);
}

.races details {
  margin-top: 0.75rem;
  border-top: 1px solid var(--edge);
  padding-top: 0.75rem;
}

.races summary {
  cursor: pointer;
  color: var(--accent);
  font-size: var(--text-sm);
}

.races details table { margin-top: 0.5rem; }

/*
 * Bande de difficulté : elle accompagne le chiffre, elle ne le remplace pas. D'où une
 * seconde ligne discrète plutôt qu'une pastille colorée — un aplat de couleur ferait de la
 * bande la valeur principale, alors que c'est le chiffre qui se compare d'un site à l'autre.
 */
.difficulty-band {
  display: block;
  color: var(--ink-muted);
  font-size: var(--text-xs);
  white-space: nowrap;
}

.difficulty-formula { margin-top: 0.5rem; }

.difficulty-formula summary {
  cursor: pointer;
  color: var(--accent);
  font-size: var(--text-sm);
}

/*
 * Accroche contextuelle : un lien dans le fil du texte, jamais un second bloc coloré. La
 * page en porte déjà un, en pied ; deux bannières se lisent comme de la publicité, un lien
 * qui répond à la section qu'on vient de lire se lit comme une suite.
 */
.inline-cta {
  margin: 0.75rem 0 0;
  font-size: var(--text-sm);
}

.inline-cta a { font-weight: 600; }

/* Index d'une course : liste d'éditions en grille, altitude sous le nom. */
.edition-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 0.75rem;
}

.edition-list a {
  display: inline-block;
  padding: 0.25rem 0.6rem;
  border: 1px solid var(--edge);
  border-radius: var(--radius-md);
  font-variant-numeric: tabular-nums;
  text-decoration: none;
}

.race-elevation {
  display: block;
  color: var(--ink-muted);
  font-size: var(--text-xs);
  font-weight: 400;
}

.edition-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  margin: 0 0 1rem;
  font-size: var(--text-sm);
}

/*
 * ---------------------------------------------------------------------------
 * FAQ
 *
 * Rendue à partir de la même liste que le FAQPage du JSON-LD, qui n'apparaissait nulle part
 * dans le corps : les règles de résultats enrichis exigent que le contenu balisé soit visible.
 * Discrète — ce sont des chiffres déjà donnés plus haut, reformulés en question.
 */
.faq-item { margin: 0 0 1rem; }

.faq-item h3 {
  display: block;
  font-size: 1rem;
  margin: 0 0 0.25rem;
}

.faq-item p {
  margin: 0;
  color: var(--ink-soft);
}

/* Où se trouve un versant, sous son titre : commune, département — et rien de plus. */
.ascent-where {
  margin: 0 0 0.5rem;
  color: var(--ink-muted);
  font-size: var(--text-sm);
}

/* Répertoire d'une course : le repli garde la mise en forme du tableau qu'il prolonge. */
.ranking details { margin-top: 0.75rem; }

.ranking summary {
  cursor: pointer;
  color: var(--accent);
  font-size: var(--text-sm);
}

.ranking details .table-scroll { margin-top: 0.5rem; }

/*
 * ---------------------------------------------------------------------------
 * En-tête, pied de page, et continuité avec la landing
 *
 * Ces pages sont la porte d'entrée d'un lecteur qui n'a jamais vu le produit, et elles
 * étaient visuellement un autre site : la landing est blanche, centrée, avec des boutons en
 * pilule et un point d'accent sur le mot-marque ; celle-ci était un document gris-bleu avec
 * « Sleipnir » en texte nu. Le point d'accent et la pilule sont repris ici — pas la mise en
 * page, qui doit rester celle d'un document lu froid depuis un moteur de recherche.
 */
.wordmark::after {
  content: ".";
  color: var(--accent);
}

.site-head nav {
  gap: 1rem;
}

/*
 * L'appel à l'action était en bas d'une page de 3 700 px, c'est-à-dire là où personne ne
 * l'atteint. Ici il est atteignable à tout moment, et il ne pousse pas les liens de
 * navigation dehors : sur un écran étroit la barre passe à la ligne (`flex-wrap` du bloc).
 */
.head-cta {
  display: inline-block;
  padding: 0.375rem 0.875rem;
  border: 1px solid var(--accent);
  border-radius: 999px;
  text-decoration: none;
  font-weight: 600;
  font-size: var(--text-sm);
  white-space: nowrap;
}

.head-cta:hover { background: var(--surface); }

/*
 * Le pied de page était un cul-de-sac : une ligne d'attribution et un lien. Un lecteur
 * arrivé sur une fiche depuis un moteur n'avait aucun chemin vers le produit, et les pages
 * légales — obligatoires, et qui n'existent que sur la landing — n'étaient nulle part.
 */
.site-foot {
  align-items: flex-start;
  gap: 1rem 2rem;
}

.foot-links {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem 1.25rem;
  padding: 0;
  margin: 0;
}

/*
 * ---------------------------------------------------------------------------
 * Les chiffres du col, en tête de fiche
 *
 * La fiche ouvrait sur « Localisation » — département, altitude, coordonnées — c'est-à-dire
 * sur ce qu'un cycliste vient chercher en dernier. Distance, dénivelé, pente et difficulté
 * n'existaient que dans le tableau tronqué plus bas et dans la prose de la FAQ. La page d'un
 * versant, elle, faisait déjà l'inverse : c'est sa forme qui est reprise ici.
 */
.col-figures {
  margin: 1.5rem 0 0;
  padding: 1rem 1.25rem;
  background: var(--surface);
  border: 1px solid var(--edge-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
}

.col-figures dd { font-size: 1.375rem; }

.col-figures .figures-note {
  margin: 0.75rem 0 0;
  color: var(--ink-muted);
  font-size: var(--text-sm);
}

/*
 * Grille des chiffres d'un versant. Elle était en `minmax(9rem, 1fr)`, donc les libellés
 * longs — « Le plus raide sur 500 m », « Portion non revêtue » — passaient à la ligne pendant
 * que leurs voisins tenaient sur une, ce qui décalait les valeurs les unes par rapport aux
 * autres et laissait un dernier item orphelin sur sa propre rangée. Un pas plus large tient
 * le libellé le plus long sur une ligne.
 */
.ascent-figures dl {
  grid-template-columns: repeat(auto-fit, minmax(11.5rem, 1fr));
  gap: 1rem 1.5rem;
}

.ascent-figures .hard-numbers { margin-top: 1.25rem; }

/*
 * ---------------------------------------------------------------------------
 * Départements
 *
 * Une colonne de 22 entrées faisait 1 800 px de haut avec la moitié droite vide. La grille
 * les rend d'un coup d'œil, et l'ordre est désormais alphabétique côté serveur — il suivait
 * le code INSEE, donc un lecteur voyait un alphabet cassé (Puy-de-Dôme avant Hautes-Pyrénées).
 */
.department-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap: 0.25rem 1.5rem;
  margin-top: 0.5rem;
}

.department-grid .department h3 { margin: 0.75rem 0 0.125rem; }

.department-grid .count { margin: 0; }

/*
 * Le groupe des cols dont le département n'est pas résolu. Il n'est pas un département : il
 * n'a pas de page, et l'annoncer comme les autres a fait publier « Col du Tourmalet — Hors
 * département » en titre de page. D'où une carte à part, en fin de liste, avec sa phrase.
 */
.department-outside {
  margin-top: 1.5rem;
  padding: 0.875rem 1.125rem;
  background: var(--surface);
  border: 1px solid var(--edge-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}

.department-outside h3 { margin: 0 0 0.25rem; }

/*
 * ---------------------------------------------------------------------------
 * Filtre de l'index (amélioration progressive)
 *
 * Injecté par cols.js et par lui seul : sans JavaScript rien de tout ceci n'existe, la page
 * reste exactement ce qu'elle est — 134 lignes dans la réponse initiale, lisibles par un
 * robot. Le style vit ici plutôt que dans le script pour que le garde CSP (`script-src
 * 'self'`) n'ait jamais à autoriser de style en ligne.
 */
.table-filter {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  margin: 0 0 0.75rem;
}

.table-filter label {
  font-size: var(--text-sm);
  color: var(--ink-muted);
}

.table-filter input,
.table-filter select {
  font: inherit;
  font-size: var(--text-sm);
  padding: 0.375rem 0.625rem;
  border: 1px solid var(--edge);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--ink);
}

.table-filter input { min-width: 12rem; }

.filter-count {
  color: var(--ink-muted);
  font-size: var(--text-sm);
  margin-left: auto;
}

/* Une recherche sans résultat le dit, plutôt que de laisser un tableau vide. */
.filter-empty {
  margin: 0.75rem 0 0;
  color: var(--ink-muted);
}

/*
 * Une ligne d'archive sans fiche n'est pas un lien cassé. Sur la page d'une course, 94 % du
 * répertoire n'a pas de page : seule la couleur les distinguait, ce qui se lit comme des
 * liens qui ne marchent pas. L'encre atténuée dit « pas de page », la note sous le tableau
 * dit pourquoi.
 */
.undocumented { color: var(--ink-muted); }

/* ------------------------------------------------------------------ carnet --
 * Le carnet méthodologique (/articles) partage cette feuille avec les cols : même produit,
 * même palette — un lecteur qui passe d'une fiche à un article ne traverse aucune frontière
 * visuelle. Ce bloc ne porte que ce que les cols n'ont pas : une liste d'articles, une ligne
 * de signature datée, et un bloc de formule (une formule se lit en chasse fixe, comme un
 * chiffre se lit en tabulaire).
 */
.article-list {
  list-style: none;
  padding: 0;
  margin: 1.5rem 0;
}

.article-card {
  background: var(--surface);
  border: 1px solid var(--edge-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: 1rem 1.25rem;
  margin-bottom: 0.75rem;
}

.article-card h2 {
  margin: 0 0 0.25rem;
}

.article-meta {
  color: var(--ink-muted);
  font-size: var(--text-xs);
  margin: 0.25rem 0 1rem;
}

.formula {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  background: var(--surface);
  border: 1px solid var(--edge-soft);
  border-radius: var(--radius-lg);
  padding: 0.75rem 1rem;
  overflow-x: auto;
}

.limits li {
  margin-bottom: 0.5rem;
}

/* ------------------------------------------------------------------ ski --
 *
 * Les pages nordiques (/ski-de-fond) partagent cette feuille avec les cols et le carnet, et
 * c'est une décision : même produit, même palette, donc un lecteur qui passe d'une fiche de
 * col à un domaine ne traverse pas de frontière visuelle. Ce bloc n'ajoute que ce que les
 * classes existantes ne couvrent pas — les deux SVG que les rendus nordiques émettent sous
 * leurs propres noms, et les quelques blocs propres à un réseau de pistes.
 */

/*
 * Les deux dessins, mêmes règles que `.col-map` : un SVG qui ne déclare pas sa largeur sort à
 * sa taille intrinsèque et déborde du cadre sur un téléphone. `height: auto` garde le rapport
 * d'aspect du viewBox, donc le tracé ne s'écrase jamais.
 */
.ski-map,
.ski-profile {
  display: block;
  width: 100%;
  height: auto;
}

/* Un profil suit sa carte : la marge est ce qui empêche les deux dessins de se lire comme un. */
.ski-profile { margin-top: 0.5rem; }

.ski-group { margin-bottom: 2.5rem; }

.ski-group h2 { margin-bottom: 0.5rem; }

/*
 * Le nom d'une piste ou d'un domaine est la seule colonne dont le contenu est une PHRASE —
 * « Boucle des Saisies par le Praz » — donc la seule qui gagne à passer à la ligne. Toutes les
 * autres sont des nombres, et un nombre coupé en deux lignes est pire qu'un tableau qui défile.
 * C'est la règle que `.cols-table td.main-ascent` porte déjà de l'autre côté.
 */
.ski-table th.ski-name {
  white-space: normal;
  min-width: 10rem;
}

/*
 * La pastille de difficulté est dessinée DANS la cellule, à gauche du mot : la couleur est le
 * code que la station peint sur ses propres panneaux, donc un lecteur la reconnaît avant de
 * lire — et sans elle le tableau perd la moitié de ce qu'il dit.
 */
.ski-table .swatch { margin-right: 0.4rem; vertical-align: -0.1em; }

/*
 * Le tableau de chiffres d'une piste : deux colonnes, la grandeur puis sa valeur. Une liste de
 * définitions serait le balisage le plus juste et ne s'aligne pas ; ici les valeurs doivent se
 * lire en colonne, ce qui est précisément ce qu'un tableau fait.
 */
.ski-figures th[scope='row'] {
  color: var(--ink-soft);
  font-weight: 600;
  width: 45%;
}

/*
 * La légende des bandes de pente en descend une par ligne au lieu de se répartir en ligne :
 * elles sont neuf au plus, chacune porte un intervalle ET une part, et une rangée qui passe à
 * la ligne au milieu d'un libellé se lit comme deux entrées.
 */
.ski-bands {
  flex-direction: column;
  gap: 0.4rem;
}

/*
 * L'ouverture du domaine est une saisie éditoriale — la seule phrase de ces pages qu'aucun
 * calcul ne produit — donc elle se distingue des chiffres qui l'entourent.
 */
.season-note {
  background: var(--surface);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-lg);
  padding: 0.625rem 1rem;
  color: var(--ink-soft);
}
