/*
 * Feuille de style du site vitrine CleanClac.
 *
 * Les jetons sont ceux de l'application (lib/theme/app_theme.dart) : même teal,
 * mêmes rayons, mêmes ombres douces. Un visiteur qui passe du site à l'app ne
 * doit pas avoir l'impression de changer de marque.
 *
 * Deux partis pris assumés :
 *
 * 1. UNE SEULE feuille, servie en statique, sans framework. La page est du HTML
 *    rendu au serveur ; y ajouter une couche de JavaScript ne rendrait rien
 *    plus lisible et coûterait le premier affichage, qui est précisément ce que
 *    mesurent Google et le visiteur.
 * 2. LE TEXTE EST GRAND. Les titres montent jusqu'à 5,5 rem sur grand écran.
 *    C'est le geste central de la maquette : on lit une promesse, pas un
 *    paragraphe.
 */

/* ------------------------------------------------------------------ police */

@font-face {
  font-family: 'Plus Jakarta Sans';
  src: url('/fonts/PlusJakartaSans-Regular.ttf') format('truetype');
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: 'Plus Jakarta Sans';
  src: url('/fonts/PlusJakartaSans-SemiBold.ttf') format('truetype');
  font-weight: 600;
  font-display: swap;
}
@font-face {
  font-family: 'Plus Jakarta Sans';
  src: url('/fonts/PlusJakartaSans-ExtraBold.ttf') format('truetype');
  font-weight: 800;
  font-display: swap;
}

/* ------------------------------------------------------------------ jetons */

:root {
  /* Hauteur de la barre du haut. Le hero s'en sert pour occuper exactement ce
     qui reste de l'écran. */
  --header-h: 100px;

  --accent: #0f766e;
  --accent-dark: #0b5b55;
  --accent-soft: #e6f2f0;
  --ink: #1a1a1a;
  --subtle: #6a6a6a;
  --faint: #9a9a9a;
  --hairline: #ebebeb;
  /* NOTE : `--subtle` reste le gris des MENTIONS (rythme d'une tâche, note de
     bas de section, libellé d'un chiffre). Les textes de description, eux, sont
     à l'encre et un cran plus gras : ils se lisent, ils ne s'effleurent pas. */
  --surface: #f7f7f7;
  --canvas: #ffffff;
  --card: #ffffff;

  --r-md: 12px;
  --r-lg: 16px;
  --r-xl: 22px;
  --r-2xl: 28px;
  --pill: 999px;

  --shadow: 0 6px 16px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.04);
  --shadow-raised: 0 12px 32px rgba(0, 0, 0, 0.1), 0 2px 6px rgba(0, 0, 0, 0.05);
  /* Les icônes qui FLOTTENT au-dessus d'un cadre, elles, portent plus loin :
     l'ombre d'une carte posée sur du blanc (10 %) se perdait sur le dégradé et
     les collait à l'écran au lieu de les en décoller. */
  --shadow-flottant: 0 18px 44px rgba(0, 0, 0, 0.2), 0 4px 10px rgba(0, 0, 0, 0.09);

  /* Respiration verticale d'une section. Elle change avec l'écran : sur mobile,
     le même vide paraît deux fois plus grand. */
  --gap-section: clamp(64px, 9vw, 128px);
  --wrap: 1200px;

  /* La gouttière des rangées pleine largeur : la colonne de contenu sur grand
     écran, la marge de page sur petit. Les deux bouts s'y alignent. Déclarée
     ICI et pas sur une rangée : la bibliothèque de tâches et la rangée
     d'arguments s'en servent toutes les deux, et une variable posée sur l'une
     n'existe pas pour l'autre. */
  --gouttiere-rail: max(24px, calc((100vw - 1200px) / 2));

  /* Où commence le téléphone, mesuré sur celui du hero : la hauteur de la
     barre, plus les 55 px que lui laisse la mise en page du hero (le cadre y
     déborde son conteneur de trois pixels vers le haut). Les vitrines
     s'y alignent, pour que le cadre soit au même endroit d'un bout à l'autre de
     la page. */
  --tel-haut: calc(var(--header-h) + 55px);

  /* La LONGUEUR DE LIGNE des descriptions. Au-delà, l'oeil perd le début de la
     ligne suivante, et une description qui court sur toute la colonne se lit
     comme un paragraphe de contrat. */
  --mesure: 450px;

  /* Le FOND des pages pleines : hero, vitrines, et les deux pages de la rangée.
     Blanc franc sous la barre du haut, puis le gris qui descend. Démarré à
     zéro, un gris très clair passait derrière l'en-tête translucide et s'y
     lisait comme une salissure. */
  --fond-degrade: linear-gradient(
    180deg,
    var(--canvas) 0,
    var(--canvas) calc(var(--header-h) + 16px),
    #f6f6f6 62%,
    #ededed 100%
  );
}

/* -------------------------------------------------------------- fondations */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  /* Un ancrage ne doit pas passer sous l'en-tête collant. */
  scroll-padding-top: 120px;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--canvas);
  color: var(--ink);
  font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img {
  max-width: 100%;
  height: auto;
}

a {
  color: inherit;
  text-decoration: none;
}

h1,
h2,
h3 {
  margin: 0;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.04;
  text-wrap: balance;
}

h1 {
  font-size: clamp(2.6rem, 6.2vw, 5.2rem);
}

h2 {
  font-size: clamp(2.1rem, 4.4vw, 3.6rem);
}

h3 {
  font-size: clamp(1.25rem, 1.8vw, 1.6rem);
  letter-spacing: -0.02em;
  line-height: 1.2;
}

p {
  margin: 0;
}

ul,
ol {
  margin: 0;
  padding: 0;
  list-style: none;
}

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin: 0 auto;
  padding: 0 24px;
}

.wrap-narrow {
  max-width: 780px;
}

/* Lien d'évitement : invisible jusqu'au premier tabulateur. */
.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  background: var(--ink);
  color: #fff;
  padding: 12px 20px;
  border-radius: 0 0 var(--r-md) 0;
}
.skip:focus {
  left: 0;
}

/* ------------------------------------------------------------------ boutons */

.btn {
  /* Un <button> herite du contour du navigateur ; un <a> non. Les deux portent
     la meme classe, le meme bouton ne peut pas se dessiner de deux facons. */
  border: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  /* Padding VOLONTAIREMENT asymétrique : 17 en haut, 21 en bas. La police a
     des métriques hautes, et à padding égal le libellé se posait deux pixels
     trop bas dans la pastille. Le centrage optique ne suit pas le centrage
     géométrique. */
  padding: 17px 30px 21px;
  /* Le MÊME arrondi que les boutons d'action de l'application (AppRadius.md,
     12 px). Pas une pastille : dans l'app, seul le sélecteur d'onglets est
     rond, jamais un bouton plein. */
  border-radius: var(--r-md);
  font-weight: 700;
  font-size: 1.02rem;
  line-height: 1;
  /* Le bouton ne bouge PAS au survol : seule la couleur change. Il ne
     s'enfonce qu'à l'appui, ce qui répond au doigt sans agiter la page. */
  transition:
    transform 0.1s ease,
    background-color 0.16s ease;
  white-space: nowrap;
}
.btn:active {
  transform: scale(0.97);
}

.btn-primary {
  background: var(--accent);
  color: #fff;
}
.btn-primary:hover {
  background: var(--accent-dark);
}

.btn-ghost {
  background: transparent;
  color: var(--ink);
  border: 1.5px solid var(--ink);
}
.btn-ghost:hover {
  background: var(--ink);
  color: #fff;
}

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 32px;
}
.actions-center {
  justify-content: center;
}

/* ------------------------------------------------------------------ en-tête */

.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgba(255, 255, 255, 0.88);
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  /* Le filet est une ombre INTERNE, pas une bordure : une bordure s'ajoute à la
     hauteur de la barre, et le hero, calé sur « tout ce qui reste », se
     retrouvait un pixel trop haut. */
  box-shadow: inset 0 -1px 0 transparent;
  transition:
    box-shadow 0.2s ease,
    opacity 0.25s ease;
}
/* Sur le pied de page, la barre disparaît : une bande blanche en travers d'un
   écran noir cassait la dernière image, et le pied porte déjà la navigation. */
.site-header.sur-pied {
  opacity: 0;
  pointer-events: none;
}
/* AUCUN filet sous l'en-tête, à aucun moment du défilement. Les sections qui
   passent dessous sont des aplats et des dégradés sans bord : une ligne en
   travers y dessinait une séparation qui n'existe pas. La barre tient toute
   seule par son flou et sa transparence. */

.header-inner {
  /* Trois colonnes, celle du milieu libre : c'est la seule façon de centrer
     vraiment les onglets, quelle que soit la largeur de la marque et du
     bouton. En `space-between`, ils se décalaient dès que l'un des deux
     changeait de taille. */
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 24px;
  position: relative;
  height: var(--header-h);
  /* La barre est PLUS LARGE que le contenu : elle suit les bords de l'écran au
     lieu de s'arrêter sur la colonne de 1200. La marque et l'action encadrent
     la page, elles n'en font pas partie. */
  max-width: 1320px;
  padding: 0 24px;
}

.header-inner > .btn {
  justify-self: end;
}

/* Hauteur imposée, largeur LIBRE : à dimensions carrées, la signature
   horizontale se retrouvait écrasée. */
.brand img {
  display: block;
  height: 30px;
  width: auto;
}

/* ---------------------------------------------------------------------- hero */

.hero {
  --hero-pad: clamp(32px, 5vw, 72px);
  padding: var(--hero-pad) 0 0;
  background: var(--fond-degrade);
  /* Le hero occupe TOUT ce qui reste sous l'en-tête : au premier coup d'oeil,
     on ne doit rien voir de la section suivante. `svh` et pas `vh` : sur
     mobile, `vh` compte la barre d'adresse comme si elle n'existait pas, et le
     bloc dépassait de sa hauteur au chargement. */
  display: flex;
  min-height: calc(100svh - var(--header-h));
  /* Le hero reste EN PLACE et la page glisse par-dessus. Il est collé juste
     sous la barre, et tout ce qui suit passe devant lui. C'est du `sticky`,
     pas du parallaxe : rien ne se déplace à une autre vitesse, le hero ne
     bouge simplement pas. Aucun JavaScript, donc aucune saccade au
     défilement. */
  position: sticky;
  top: var(--header-h);
  z-index: 0;
}
.hero > .wrap {
  flex: 1;
}

.hero-grid {
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  align-items: center;
  gap: clamp(32px, 5vw, 72px);
}

.hero-plain .hero-grid {
  grid-template-columns: 1fr;
  max-width: 860px;
  text-align: center;
  /* Sans visuel a cote, le titre EST le hero : il se centre sur la hauteur
     disponible sous la barre. La marge basse du bloc de texte, qui sert a
     remonter le titre en face d'un telephone, n'a plus lieu d'etre ici : elle
     le decalait vers le haut, et le rembourrage du hero le renvoyait vers le
     bas. Les deux se voyaient. */
  align-content: center;
}
.hero-plain {
  --hero-pad: 0px;
}
.hero-plain .actions {
  justify-content: center;
}

.hero-text {
  /* Le titre se centre sur L'ECRAN ENTIER, barre du haut comprise : elle flotte
     au-dessus du degrade du hero, l'oeil voit donc une seule page pleine et
     mesure a partir du bord, pas de sous la barre.
     Le calcul, pas une valeur choisie a l'oeil : la grille centre le titre dans
     la boite REMBOURREE, soit un demi-rembourrage sous le centre du hero,
     lui-meme une demi-barre sous le centre de l'ecran. Une marge basse remonte
     un element centre de la MOITIE de sa valeur : il faut donc la barre plus le
     rembourrage pour annuler les deux, a toutes les tailles d'ecran.
     Le telephone, lui, garde son ancrage haut : il doit rester en face de ceux
     des vitrines. */
  margin-bottom: calc(var(--header-h) + var(--hero-pad));
}

.hero .lead {
  max-width: var(--mesure);
  margin-top: 24px;
  font-size: clamp(1.12rem, 1.5vw, 1.38rem);
  color: var(--ink);
  font-weight: 500;
  max-width: 40ch;
}
.hero-plain .lead {
  margin-inline: auto;
}

.note {
  margin-top: 18px;
  color: var(--faint);
  font-size: 0.95rem;
}
.note-center {
  text-align: center;
}

/* --------------------------------------------------------------- illustrations */

.art {
  margin: 0;
  display: grid;
  place-items: center;
}
.art img {
  width: min(100%, 260px);
  height: auto;
  filter: drop-shadow(0 18px 32px rgba(0, 0, 0, 0.14));
}

/* Cadre de téléphone : dessiné en CSS, pour que la capture d'app ait toujours
   l'air posée dans un appareil, quelle que soit l'image déposée. */
.phone {
  margin: 0;
  display: grid;
  place-items: center;
}
.phone-screen {
  position: relative;
  display: block;
  width: min(100%, 320px);
  /* Repère de LARGEUR pour l'îlot des capteurs (voir `::after`). Sans lui, son
     décalage se mesurait sur la HAUTEUR du cadre, qui dépend de la capture
     posée dedans : l'ovale tombait deux crans plus bas d'une page à l'autre. */
  container-type: inline-size;
  /* PAS de proportion imposée quand la capture est là : c'est elle qui donne
     sa hauteur au cadre. Avec une proportion fixe, l'écran ne tombait pas
     exactement sur celle de l'image (le cadre ajoute 10 px de chaque côté) et
     le recadrage mangeait discrètement la gauche et la droite. */
  min-height: 340px;
  padding: 10px;
  border-radius: 46px;
  background: #111;
  box-shadow: var(--shadow-raised);
  overflow: hidden;
}
/* L'écran, DERRIÈRE la capture : tant qu'aucune image n'est déposée, le
   téléphone montre un écran clair au lieu d'un rectangle noir. */
/* Emplacement encore vide : le cadre reprend la silhouette d'un téléphone,
   sinon il s'écrasait en pastille et on ne comprenait plus ce qui manquait. */
.phone-screen:not(:has(img:not([hidden]))) {
  aspect-ratio: 390 / 844;
}

.phone-screen::before {
  content: '';
  position: absolute;
  inset: 10px;
  border-radius: 36px;
  background: #fff;
}
/* `display:block` l'emporte sur l'attribut `hidden` : sans cette règle, une
   capture pas encore déposée affichait son texte de remplacement en plein
   milieu du téléphone. */
.phone-screen img[hidden] {
  display: none;
}
.phone-screen img {
  position: relative;
  display: block;
  width: 100%;
  height: auto;
  border-radius: 36px;
  background: #fff;
}

/* Plusieurs captures dans le même téléphone. La PILE porte la proportion des
   captures (toutes sont prises sur le même appareil) : les images étant hors
   flux, sans elle le cadre n'aurait plus de hauteur du tout. */
.shots {
  position: relative;
  display: block;
  aspect-ratio: 1206 / 2552;
  border-radius: 36px;
  overflow: hidden;
  background: #fff;
}
.shots img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border-radius: 0;
  /* Au repos, chacune attend hors cadre, à DROITE ; celle du moment est en
     place. La sortante finit à gauche : le regard suit un défilé, pas un
     clignotement. */
  transform: translateX(100%);
}
.shots img.on {
  transform: translateX(0);
}
/* La classe n'est posée QUE pendant le mouvement : c'est ce qui permet de
   ramener sans bruit l'image sortie de l'autre côté avant son tour suivant. */
.shots img.anime {
  transition: transform 0.55s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Variante « mouvement réduit » : on change d'image sur place, en fondu. */
.shots img.fondu {
  transition: opacity 0.45s ease;
}

/* L'îlot des capteurs, en haut de l'écran. Sans lui, le cadre se lisait comme
   une tablette ou un cadre photo ; c'est ce petit ovale qui dit « téléphone ».
   Position ET taille en `cqw` (pourcentage de la LARGEUR du cadre) : un `top`
   en pourcentage se mesure sur la hauteur, qui change avec la capture posée
   dedans, et l'ovale se décalait d'une page à l'autre. */
.phone-screen::after {
  content: '';
  position: absolute;
  z-index: 2;
  top: calc(10px + 2.6cqw);
  left: 50%;
  transform: translateX(-50%);
  width: 31cqw;
  aspect-ratio: 125 / 37;
  border-radius: var(--pill);
  background: #111;
}

.wide {
  margin: 0;
}
.wide img {
  width: 100%;
  border-radius: var(--r-2xl);
  box-shadow: var(--shadow-raised);
  background: var(--surface);
}

/* Le téléphone du hero déborde de son cadre, volontairement : il est plus haut
   que la zone qui le contient, et on n'en voit que la partie centrale. */
.hero-media {
  position: relative;
  align-self: stretch;
  min-height: 340px;
  /* On ne rogne QUE le bas. `overflow: hidden` coupait les deux axes : le
     téléphone dépassait sa colonne de trois pixels, et ses bords arrondis se
     retrouvaient tranchés à gauche et à droite. */
  clip-path: inset(-400px -400px 0 -400px);
}
/* Positionnement ABSOLU, et pas une simple grille : en flux, le téléphone se
   faisait comprimer à la hauteur disponible et se réduisait à une pastille.
   Hors flux, il garde sa taille réelle et c'est le parent qui le rogne. */
.hero-media .phone {
  position: absolute;
  /* Remonté sous la barre : ce qui sort par le haut est le cadre, pas l'écran,
     et on gagne ces 20 px sur ce qu'on voit de la capture. */
  top: -20px;
  left: 50%;
  transform: translateX(-50%);
  /* Largeur EXPLICITE : hors flux et calé à 50 %, le cadre se réduisait tout
     seul à la place restante à sa droite, et le `min(100%, …)` de l'écran se
     mesurait sur cette largeur ratatinée. */
  width: 100%;
}
.hero-media .phone-screen {
  /* Jamais plus large que sa colonne : le rognage latéral vient de là. Moins
     large qu'avant : à hauteur visible constante, un cadre plus étroit montre
     PLUS de la capture, puisque l'image s'y range à l'échelle. */
  width: min(100%, 470px);
}

/* ------------------------------------------------------------------ sections */

.section {
  padding: var(--gap-section) 0;
  background: var(--canvas);
}

/* Le hero est en-dessous, le reste de la page au-dessus : c'est ce qui donne
   le recouvrement. Sans `position`, le `z-index` ne s'appliquerait pas. */
main > section:not(.hero),
.site-footer {
  position: relative;
  z-index: 1;
}

.section-head {
  max-width: 760px;
  margin: 0 auto clamp(40px, 5vw, 64px);
  text-align: center;
}
.section-head-left {
  margin-inline: 0;
  text-align: left;
}
/* Le titre de la page de contact est passe en h1 (elle n'avait aucun titre de
   premier niveau). Le h1 est dimensionne pour un HERO : garder sa taille ici
   aurait donne un titre de 5 rem au-dessus d'un formulaire. On lui rend la
   taille d'un titre de section, sans toucher a son rang dans le document. */
.section-head h1 {
  font-size: clamp(2.1rem, 4.4vw, 3.6rem);
}
.section-head .lead {
  max-width: var(--mesure);
  margin-inline: auto;
  margin-top: 20px;
  font-size: clamp(1.05rem, 1.3vw, 1.22rem);
  color: var(--ink);
  font-weight: 500;
}

/* Bandeaux pleine largeur (logos, chiffres, appel final). */
.band {
  padding: clamp(40px, 5vw, 72px) 0;
  background: var(--surface);
}

.band-title {
  text-align: center;
  color: var(--faint);
  font-weight: 700;
  font-size: 0.92rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin-bottom: 32px;
}

.logo-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: clamp(16px, 2vw, 24px);
}
/* Chaque logo dans sa pastille blanche, en COULEUR. Le passage en niveaux de
   gris, courant sur ce genre de bandeau, transformait des icônes carrées et
   colorées en petits carrés sales : on ne reconnaissait plus les marques, ce
   qui est précisément ce que ce bandeau doit faire. */
.logo-row li {
  display: grid;
  place-items: center;
  width: 76px;
  height: 76px;
  background: var(--card);
  border: 1px solid var(--hairline);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow);
  transition: transform 0.2s ease;
}
.logo-row li:hover {
  transform: translateY(-3px);
}
.logo-row img {
  width: 42px;
  height: 42px;
  object-fit: contain;
}

/* ------------------------------------------------------------------- cartes */

.cards {
  display: grid;
  gap: clamp(20px, 2.4vw, 32px);
}
.cards-2 {
  grid-template-columns: repeat(2, 1fr);
}
.cards-3 {
  grid-template-columns: repeat(3, 1fr);
}

.card {
  padding: clamp(28px, 3vw, 40px);
  background: var(--card);
  border: 1px solid var(--hairline);
  border-radius: var(--r-2xl);
  box-shadow: var(--shadow);
  transition:
    transform 0.2s ease,
    box-shadow 0.2s ease;
}
.card:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-raised);
}
.card .art {
  margin-bottom: 24px;
  justify-items: start;
}
.card .art img {
  width: 96px;
}
.card h3 {
  margin-bottom: 12px;
}
.card p {
  color: var(--ink);
  font-weight: 500;
}

.audience .card {
  padding: clamp(32px, 3.4vw, 48px);
  background: var(--card);
}
.audience .art img {
  width: 132px;
}

/* -------------------------------------------------------------------- étapes */

.steps-grid {
  display: grid;
  grid-template-columns: 1.1fr 0.9fr;
  align-items: center;
  gap: clamp(32px, 5vw, 80px);
}

/* Sans illustration, la liste reprend toute la largeur utile au lieu de laisser
   une demi-page blanche à côté d'elle. */
.steps-solo {
  grid-template-columns: 1fr;
  max-width: 860px;
}

.steps {
  counter-reset: step;
  display: grid;
  gap: 32px;
}
.steps li {
  position: relative;
  counter-increment: step;
  padding-left: 68px;
}
.steps li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: -4px;
  width: 46px;
  height: 46px;
  display: grid;
  place-items: center;
  border-radius: var(--pill);
  background: var(--accent);
  color: #fff;
  font-weight: 800;
  font-size: 1.15rem;
}
.steps p {
  margin-top: 8px;
  color: var(--ink);
  font-weight: 500;
}

/* ------------------------------------------------------------- deux colonnes */

.split-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  gap: clamp(32px, 5vw, 88px);
}
.split-flip .split-text {
  order: 2;
}
/* Le bloc occupe toute la hauteur disponible, pour que le texte se centre sur
   la PAGE et non sur le téléphone. Calé sur la seule hauteur du cadre, il
   tombait cent pixels trop bas, le cadre étant lui-même collé en bas. */
.showcase .wrap {
  flex: 1;
}
.showcase .split-grid,
.showcase .integrations-grid {
  height: 100%;
  align-items: center;
}
/* Quand le téléphone est à droite, le texte se ferre à droite : les deux
   colonnes se rejoignent au milieu au lieu de laisser une gouttière molle
   entre une fin de ligne et un cadre. Le bloc de texte suit : sans ça, sa
   largeur maximale le laissait accroché à gauche pendant que les lignes, elles,
   partaient à droite. */
.showcase:not(.split-flip) .split-text {
  text-align: right;
}
.showcase:not(.split-flip) .split-text .lead {
  margin-left: auto;
}
.split .lead {
  /* 50 px de plus que la mesure commune : la colonne d'une vitrine est plus
     large que celle d'un chapeau de section, et le texte y tenait sur une
     ligne de trop. */
  max-width: calc(var(--mesure) + 50px);
  margin-top: 24px;
  font-size: clamp(1.05rem, 1.3vw, 1.2rem);
  color: var(--ink);
  font-weight: 500;
}

.ticks {
  display: grid;
  gap: 14px;
  margin-top: 28px;
}
.ticks li {
  position: relative;
  padding-left: 34px;
  color: var(--ink);
}
.ticks li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 7px;
  width: 20px;
  height: 20px;
  border-radius: var(--pill);
  background: var(--accent-soft);
}
/* La coche elle-même : deux bords tournés à 45°, pas une image à télécharger.
   Un dégradé croisé, tenté d'abord, dessinait une croix et non un accord. */
.ticks li::after {
  content: '';
  position: absolute;
  left: 7px;
  top: 10px;
  width: 5px;
  height: 9px;
  border-right: 2px solid var(--accent);
  border-bottom: 2px solid var(--accent);
  transform: rotate(45deg);
}
.ticks-cols {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: 32px;
}

/* -------------------------------------------------------------------- tarifs */

.price-card {
  max-width: 680px;
  margin: 0 auto;
  /* De GROS rembourrages : la carte porte quatre choses, elle doit respirer
     comme les cartes de l'application. */
  padding: clamp(28px, 3.5vw, 44px) clamp(24px, 4vw, 56px) clamp(32px, 4vw, 52px);
  background: var(--card);
  border: 1px solid var(--hairline);
  border-radius: var(--r-2xl);
  box-shadow: var(--shadow);
  text-align: center;
}

/* Les onglets sont LES pastilles du sélecteur de public : même composant, mêmes
   règles (voir `.tab`). Ici ce sont des <button>, il ne reste qu'à leur retirer
   l'habillage natif. */
.cycle {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 13px;
  /* HAUTEUR FIXE, celle de la pastille active. Sans elle, passer d'un onglet à
     l'autre faisait grandir la rangée de six pixels et toute la carte
     sursautait. */
  height: 48px;
}
.cycle .tab {
  gap: 8px;
  border: 0;
  font-family: inherit;
  cursor: pointer;
}
/* La remise est VERTE, toujours, y compris sur la pastille en encre : c'est la
   couleur du projet, et celle que porte la page abonnement de l'application. */
.cycle-badge {
  padding: 5px 9px;
  border-radius: var(--pill);
  background: var(--accent);
  color: #fff;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: -0.01em;
  line-height: 1;
}

/* Le prix, très gros : c'est la seule chose que la page a vraiment à dire. */
.price-big {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 6px;
  margin-top: clamp(36px, 5vw, 60px);
}
.price-big b {
  /* La typo des TITRES, au chiffre près : même graisse, même approche, même
     interlignage. C'est le seul endroit de la page où un nombre est un titre. */
  font-size: clamp(2.6rem, 5.4vw, 4.2rem);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.04;
}
.price-big i {
  font-style: normal;
  font-size: clamp(1rem, 1.4vw, 1.15rem);
  font-weight: 600;
  color: var(--subtle);
}
.price-note {
  margin-top: 8px;
  min-height: 1.4em;
  color: var(--faint);
  font-size: 0.95rem;
}

/* Le curseur : de un à cinquante logements, le prix suit. */
.price-slider {
  display: block;
  margin-top: clamp(40px, 5.5vw, 64px);
}
.price-count {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 16px;
  font-weight: 700;
}
.price-pastille {
  display: inline-grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--surface);
  color: var(--ink);
}
.price-pastille svg {
  width: 19px;
  height: 19px;
}
.price-slider input[type='range'] {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 8px;
  border-radius: var(--pill);
  /* La part parcourue est peinte à l'accent, le reste en gris : `--p` est posé
     par le script (et par le rendu, pour l'état de départ). */
  background: linear-gradient(
    90deg,
    var(--accent) 0,
    var(--accent) var(--p, 0%),
    var(--hairline) var(--p, 0%),
    var(--hairline) 100%
  );
  cursor: pointer;
}
.price-slider input[type='range']::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: #fff;
  border: 1px solid var(--hairline);
  box-shadow: var(--shadow);
  cursor: grab;
}
.price-slider input[type='range']::-moz-range-thumb {
  width: 28px;
  height: 28px;
  border: 1px solid var(--hairline);
  border-radius: 50%;
  background: #fff;
  box-shadow: var(--shadow);
  cursor: grab;
}
.price-slider input[type='range']:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 6px;
}
.included {
  max-width: 900px;
  /* Large au-dessus ET en-dessous : la liste est un bloc a part, pas la suite
     de la carte tarifaire, et la section qui suit doit demarrer au large. */
  margin: clamp(72px, 9vw, 128px) auto clamp(40px, 5vw, 72px);
}
.included h3 {
  margin-bottom: clamp(36px, 4.5vw, 64px);
  text-align: center;
}

/* ------------------------------------------------------------------ chiffres */

.stats ul {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 32px;
  text-align: center;
}
.stats b {
  display: block;
  font-size: clamp(2.4rem, 4.4vw, 3.6rem);
  font-weight: 800;
  letter-spacing: -0.03em;
  color: var(--accent);
  line-height: 1.05;
}
.stats span {
  display: block;
  margin-top: 10px;
  color: var(--subtle);
}

/* ----------------------------------------------------------------------- FAQ */

.faq details {
  border-bottom: 1px solid var(--hairline);
}
.faq summary {
  cursor: pointer;
  list-style: none;
  padding: 26px 44px 26px 0;
  position: relative;
  font-size: clamp(1.05rem, 1.5vw, 1.28rem);
  font-weight: 700;
  letter-spacing: -0.01em;
}
.faq summary::-webkit-details-marker {
  display: none;
}
.faq summary::after {
  content: '';
  position: absolute;
  right: 6px;
  top: 50%;
  width: 12px;
  height: 12px;
  border-right: 2.5px solid var(--faint);
  border-bottom: 2.5px solid var(--faint);
  transform: translateY(-70%) rotate(45deg);
  transition: transform 0.2s ease;
}
.faq details[open] summary::after {
  transform: translateY(-30%) rotate(-135deg);
}
.faq .answer {
  padding: 0 44px 28px 0;
  color: var(--ink);
  font-weight: 500;
  font-size: 1.05rem;
}

.empty {
  text-align: center;
  color: var(--faint);
  padding: 48px 0;
  border: 1px dashed var(--hairline);
  border-radius: var(--r-2xl);
}

/* Le visuel du bloc PRIX est plus grand que les autres : la maquette et le
   gobelet ne valent que par leur comparaison de taille, et à 300 px on ne
   voyait plus qu'ils font la même hauteur. */
.split .art:has(img[src$='price_coffee.webp']) img {
  /* Sur grand ecran, la colonne a de la place : le gobelet et la maquette se
     comparent d'autant mieux qu'ils sont grands. Le `min(100%, ...)` garde la
     borne mobile intacte, ou c'est la largeur de colonne qui commande. */
  width: min(100%, clamp(360px, 32vw, 520px));
}

/* ------------------------------------------------------------------ contact */

.contact-form {
  display: grid;
  gap: 18px;
  margin-top: clamp(32px, 4vw, 48px);
  text-align: left;
}
.champ {
  display: grid;
  gap: 8px;
}
.champ > span {
  font-weight: 600;
  font-size: 0.95rem;
}
/* Les MOTIFS : des blocs a choisir, pas un menu deroulant. Une grille
   d'images se lit d'un coup d'oeil, la ou un menu demande d'ouvrir puis de
   tout lire pour choisir. Ce sont de vrais boutons radio, caches : le clavier,
   les fleches et les lecteurs d'ecran fonctionnent sans une ligne de script. */
.motifs {
  border: 0;
  padding: 0;
  margin: 0;
}
.motifs legend {
  padding: 0;
  margin-bottom: 10px;
  font-weight: 600;
  font-size: 0.95rem;
}
.motifs-grille {
  display: grid;
  /* Assez etroit pour que les cinq motifs tiennent sur UNE ligne dans la
     colonne de lecture : a 150 pixels le dernier tombait seul sur une
     deuxieme rangee, et on le lisait comme une categorie a part. */
  grid-template-columns: repeat(auto-fit, minmax(128px, 1fr));
  gap: 12px;
}
.motif {
  display: grid;
  justify-items: center;
  gap: 8px;
  padding: 18px 14px 16px;
  border: 1px solid var(--hairline);
  border-radius: var(--r-xl);
  background: var(--card);
  text-align: center;
  cursor: pointer;
  transition:
    border-color 0.16s ease,
    box-shadow 0.16s ease;
}
.motif img {
  width: 76px;
  height: 76px;
  object-fit: contain;
}
.motif span {
  font-size: 0.92rem;
  font-weight: 600;
  line-height: 1.25;
}
/* Le bouton radio lui-meme reste dans la page (il porte l'etat et le clavier),
   mais ne se voit pas : c'est le bloc qui montre la selection. */
.motif input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}
.motif:hover {
  border-color: var(--subtle);
}
.motif:has(input:checked) {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}
/* Le focus clavier se voit sur le BLOC, puisque le bouton est invisible. */
.motif:has(input:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.champ input,
.champ select,
.champ textarea {
  width: 100%;
  padding: 14px 16px;
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  background: var(--card);
  color: var(--ink);
  font: inherit;
  font-size: 1rem;
}
.champ textarea {
  resize: vertical;
  min-height: 140px;
}
.champ input:focus-visible,
.champ select:focus-visible,
.champ textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
/* Le champ PIEGE : hors de l'ecran, jamais atteignable au clavier. `display:
   none` serait ignore par certains robots, qui le remplissent quand meme. */
.pot-de-miel {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
}
.contact-etat {
  min-height: 1.4em;
  color: var(--accent);
  font-weight: 600;
}
/* Envoye : le formulaire s'efface, il ne reste que la reponse. Le laisser
   affiche invite a renvoyer le meme message. */
.contact-envoye .champ,
.contact-envoye .actions,
.contact-envoye .note {
  display: none;
}

/* ------------------------------------------------------- choix de la langue */

.langue {
  position: relative;
}
/* Le bouton de langue est un bouton de PIED DE PAGE comme les deux autres : il
   reprend a la lettre la boite du `.store-badge` (hauteur 48, meme padding
   asymetrique, meme arrondi, meme contour). Trois boutons cote a cote dans la
   meme barre ne peuvent pas avoir trois formes. */
.langue-bouton {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  height: 48px;
  padding: 0 20px 2px;
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: var(--r-md);
  color: inherit;
  font-size: 0.95rem;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  list-style: none;
  transition:
    border-color 0.16s ease,
    background-color 0.16s ease;
}
.langue-bouton::-webkit-details-marker {
  display: none;
}
.langue-bouton:hover {
  border-color: rgba(255, 255, 255, 0.6);
  background: rgba(255, 255, 255, 0.06);
}
.langue-bouton img {
  width: 20px;
  height: auto;
  border-radius: 3px;
}
/* La feuille monte du BAS, comme dans l'application, et le voile eteint la
   page derriere elle. */
.langue-voile {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: rgba(0, 0, 0, 0.5);
  animation: voile-entre 0.24s ease both;
}
@keyframes voile-entre {
  from {
    opacity: 0;
  }
}
/* La feuille MONTE, comme celle de Flutter. `<details>` affiche son contenu
   d'un coup : c'est une animation d'apparition, pas une transition, puisqu'il
   n'y a pas d'etat precedent a interpoler. */
@keyframes feuille-monte {
  from {
    transform: translateY(100%);
  }
}
/* La FERMETURE est jouee par le script : il pose cette classe, attend la fin,
   puis retire `open`. Sans ce detour, la feuille disparaissait d'un coup. */
.langue.ferme .langue-feuille {
  animation: feuille-descend 0.2s ease both;
}
.langue.ferme .langue-voile {
  animation: voile-sort 0.2s ease both;
}
@keyframes feuille-descend {
  to {
    transform: translateY(100%);
  }
}
@keyframes voile-sort {
  to {
    opacity: 0;
  }
}
/* Pendant que le doigt tient la feuille, aucune animation ne doit lutter avec
   la position que le script ecrit. */
.langue.glisse .langue-feuille {
  animation: none;
  transition: none;
}
@media (prefers-reduced-motion: reduce) {
  .langue-feuille,
  .langue-voile,
  .langue.ferme .langue-feuille,
  .langue.ferme .langue-voile {
    animation: none;
  }
}
.langue-feuille {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 91;
  /* La MEME contrainte que `showAppSheet` : 640 de large au plus, centre. Sur
     telephone la feuille prend toute la largeur, sur ordinateur elle reste une
     feuille qui monte du bas au lieu de traverser l'ecran. */
  max-width: 640px;
  margin-inline: auto;
  max-height: 80svh;
  overflow-y: auto;
  padding: 12px 24px calc(32px + env(safe-area-inset-bottom));
  /* AppRadius.xl, comme le `RoundedRectangleBorder` du theme. */
  border-radius: var(--r-xl) var(--r-xl) 0 0;
  animation: feuille-monte 0.28s cubic-bezier(0.2, 0.8, 0.2, 1) both;
  touch-action: none;
  background: var(--card);
  color: var(--ink);
}


.langue-poignee {
  display: block;
  /* `dragHandleSize: Size(36, 4)` du theme de l'application. */
  width: 36px;
  height: 4px;
  margin: 0 auto 18px;
  border-radius: 999px;
  background: var(--hairline);
}
/* Le titre de la feuille, comme dans l'application : centre, a l'encre, en
   casse normale. Le pied de page, lui, ecrit ses intertitres en capitales
   blanches espacees ; sa regle vient plus bas dans le fichier et l'emportait a
   poids egal, ce qui donnait un titre blanc sur la carte blanche. */
.site-footer .langue-feuille h2 {
  /* De l'air des deux cotes du titre : la poignee juste au-dessus et la
     premiere langue juste en dessous le serraient, et il se lisait comme une
     etiquette posee sur la liste plutot que comme son titre. */
  margin-top: 12px;
  margin-bottom: 32px;
  font-size: 1.25rem;
  font-weight: 700;
  letter-spacing: 0;
  text-transform: none;
  text-align: center;
  color: var(--ink);
}
.langue-feuille ul {
  display: grid;
  /* AppSpace.sm entre deux options, comme la colonne de la feuille. */
  gap: 8px;
  margin: 0 auto;
}
/* Le pied de page espace ses items de liste de 12 px. Dans la feuille, cette
   marge s'ajoutait a l'ecart de la grille : 20 px au lieu de 8, et les lignes
   flottaient. */
.site-footer .langue-feuille li {
  margin-bottom: 0;
}
/* Une option = le `_LangOption` de l'application, valeur pour valeur : fond
   `surface`, rayon AppRadius.lg, 24 de padding horizontal et 18 de vertical,
   drapeau a 26, ecart de AppSpace.md, libelle a 16 en 600. */
.site-footer .langue-feuille a {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 18px 24px;
  border-radius: var(--r-lg);
  background: var(--surface);
  /* Le pied de page peint ses liens en blanc au survol : sur la carte blanche
     de la feuille, le nom de la langue disparaissait. On reprend la main, avec
     assez de poids pour passer devant `.site-footer a:hover`. */
  color: var(--ink);
  font-size: 1rem;
  font-weight: 600;
}
.site-footer .langue-feuille a:hover {
  background: var(--hairline);
  color: var(--ink);
}
/* La langue courante : fond vert pale, texte vert, et la coche a droite. C'est
   le seul etat « selectionne » de la feuille, il doit se voir sans compter les
   drapeaux. */
.site-footer .langue-feuille a[aria-current='true'],
.site-footer .langue-feuille a[aria-current='true']:hover {
  background: var(--accent-soft);
  color: var(--accent);
}
.langue-feuille .coche {
  margin-left: auto;
  width: 22px;
  height: 22px;
  flex: none;
}
.langue-feuille .nom {
  /* L'encre d'une police n'est pas centree dans sa boite : le nom tombait un
     poil bas face au drapeau. Deux pixels, comme dans l'application. */
  padding-bottom: 2px;
}
.langue-feuille img {
  width: 26px;
  height: auto;
  flex: none;
  border-radius: 3px;
}

/* ------------------------------------------------------------- appel final */

/* L'appel final prend l'écran entier, comme le pied de page qui le suit : les
   deux forment le noir de la fin de visite. Serré sur son texte, il ressemblait
   à un encart, pas à une conclusion. */
.final {
  background: var(--ink);
  color: #fff;
  text-align: center;
  /* L'appel et le pied de page se PARTAGENT le dernier ecran : deux ecrans
     noirs d'affilee coupaient la fin de visite en deux, alors que c'est un
     seul moment. */
  min-height: 58svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.final .lead {
  margin-top: 20px;
  color: rgba(255, 255, 255, 0.7);
  font-size: clamp(1.05rem, 1.3vw, 1.2rem);
}
.final .btn-ghost {
  color: #fff;
  border-color: rgba(255, 255, 255, 0.5);
}
.final .btn-ghost:hover {
  background: #fff;
  color: var(--ink);
}

/* ---------------------------------------------------------------- texte long */

.prose h1 {
  font-size: clamp(2.2rem, 4vw, 3.2rem);
  margin-bottom: 32px;
}
.prose h2 {
  font-size: clamp(1.3rem, 2vw, 1.7rem);
  margin: 48px 0 16px;
}
.prose p {
  color: var(--ink);
  font-weight: 500;
  margin-bottom: 16px;
}

/* ------------------------------------------------------------------ pied de page */

/* Le pied de page complète l'appel noir : les deux tiennent sur le même écran,
   l'appel au milieu du haut, le pied posé en bas. C'est la dernière image qu'on
   emporte, et elle est d'un seul tenant. */
.site-footer {
  background: var(--ink);
  color: rgba(255, 255, 255, 0.72);
  padding: clamp(40px, 4vw, 64px) 0 40px;
  min-height: 42svh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}
.footer-grid {
  display: grid;
  grid-template-columns: 1.6fr repeat(3, 1fr);
  gap: 40px;
}
.footer-brand img {
  height: 30px;
  width: auto;
}
.footer-brand p {
  margin-top: 18px;
  max-width: 30ch;
}

/* Les deux boutiques, en pastilles creuses : sur le noir du pied, un aplat
   blanc aurait crié plus fort que la marque juste au-dessus. */
.footer-stores {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 28px;
}
.store-badge {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  /* Deux pixels de plus EN BAS, pas au milieu : le contenu reste où il est, et
     le cadre cesse de paraître remonté. Un logo a toujours un peu de blanc
     sous la ligne de base, l'oeil le voit comme un décalage vers le haut. */
  height: 48px;
  padding: 0 20px 2px;
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: var(--r-md);
  color: #fff;
  font-weight: 700;
  font-size: 0.95rem;
  line-height: 1;
  transition:
    border-color 0.16s ease,
    background-color 0.16s ease;
}
.store-badge:hover {
  border-color: rgba(255, 255, 255, 0.6);
  background: rgba(255, 255, 255, 0.06);
}
.store-badge svg {
  width: 20px;
  height: 22px;
  flex: none;
}
.site-footer h2 {
  font-size: 0.9rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #fff;
  margin-bottom: 18px;
}
.site-footer li {
  margin-bottom: 12px;
}
.site-footer a:hover {
  color: #fff;
}
.footer-bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: 56px;
  padding-top: 28px;
  border-top: 1px solid rgba(255, 255, 255, 0.14);
  font-size: 0.92rem;
  color: rgba(255, 255, 255, 0.5);
}

/* --------------------------------------------------------- choix du public */

/* Paire de pastilles flottantes, calquée sur le sélecteur « Aujourd'hui / À
   venir » de l'application : mêmes hauteurs (48 active, 42 au repos), même
   écart de 13, même encre, même ombre. Un visiteur qui connaît l'app retrouve
   son geste. */
.tabs {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 13px;
}

.tab {
  display: inline-flex;
  align-items: center;
  height: 42px;
  /* 1 px de padding bas EN PLUS du haut : le libellé remonte d'autant. Les
     métriques de la police le posaient un cheveu trop bas dans la pastille,
     comme sur les boutons pleins (règle maison, cf. `.btn`). */
  padding: 0 26px 1px;
  border-radius: var(--pill);
  background: var(--surface);
  color: var(--ink);
  font-size: 13px;
  font-weight: 600;
  line-height: 1;
  /* AUCUNE ombre au repos : c'est le sélecteur de l'application, à l'identique
     (lib/widgets/pill_tabs.dart n'en pose que sur la pastille active). Une
     ombre ici suffisait à faire deux composants là où il n'en faut qu'un. */
  transition:
    height 0.16s ease,
    background-color 0.16s ease,
    color 0.16s ease,
    transform 0.1s ease;
}
.tab:hover {
  background: #ededed;
}
/* L'appui enfonce, comme le PressScale de l'app. */
.tab:active {
  transform: scale(0.97);
}

.tab-on {
  height: 48px;
  background: var(--ink);
  color: #fff;
  box-shadow: 0 5px 14px rgba(0, 0, 0, 0.2);
}
.tab-on:hover {
  background: var(--ink);
}

/* Petit écran : la barre ne peut pas porter la marque, les onglets ET le
   bouton. Les onglets descendent juste sous elle, toujours centrés. Position
   ABSOLUE et pas fixe : la barre porte un flou d'arrière-plan, qui fait d'elle
   le repère des éléments fixes de sa descendance. Le résultat aurait été le
   même, par accident ; on le dit donc explicitement. */
@media (max-width: 900px) {
  .header-inner {
    grid-template-columns: 1fr auto;
  }
  .tabs {
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
    margin-top: 10px;
  }
}

/* --------------------------------------------- bibliothèque de tâches */

/* Les thèmes, en FILTRES. Au repos, la couleur du thème posée en fond très
   clair, comme l'intertitre de la checklist dans l'application. Sélectionné, la
   pastille se remplit du vert de la marque : c'est le même geste que partout
   ailleurs dans le produit, et un seul thème est actif à la fois. */
.themes {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  margin: 0 0 44px;
  padding: 0;
  list-style: none;
}
.theme {
  padding: 9px 18px 11px;
  /* Les pastilles sont devenues des <button> pour être cliquables au clavier.
     Le navigateur leur pose alors SA bordure, un liseré gris foncé qui n'a rien
     à faire là. */
  border: 0;
  border-radius: var(--pill);
  font-size: 0.95rem;
  /* 600 et pas 700 : à cette taille et dans une couleur pleine, l'extra-gras
     de Jakarta empâtait le libellé et les six pastilles devenaient une barre
     de titres. */
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  color: var(--teinte);
  background: color-mix(in srgb, var(--teinte) 12%, #fff);
  transition:
    background-color 0.16s ease,
    color 0.16s ease,
    transform 0.1s ease;
}
.theme:hover {
  background: color-mix(in srgb, var(--teinte) 20%, #fff);
}
.theme-on,
.theme-on:hover {
  /* La couleur DU THÈME, pas le vert de la marque : c'est la même teinte qui
     sert de fond dilué au repos, et c'est elle qui range une tâche dans sa
     section. La pastille active dit donc de quelle couleur on parle. */
  background: var(--teinte);
  color: #fff;
}
.theme:active {
  transform: scale(0.97);
}

/* UNE seule rangée, qui file d'un bord à l'autre de l'écran. Hors de la colonne
   de contenu, volontairement : une rangée qui s'arrête aux marges ne donne pas
   envie d'être poussée, elle a l'air finie. */
/* Les flèches se posent SOUS la rangée, centrées. Sur la ligne des thèmes,
   elles poussaient les pastilles hors de leur alignement. */
.catalogue-fleches {
  display: flex;
  justify-content: center;
  gap: 12px;
  margin-top: clamp(28px, 3vw, 44px);
}
.fleche {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 1px solid var(--hairline);
  border-radius: var(--pill);
  background: var(--card);
  color: var(--ink);
  cursor: pointer;
  transition:
    border-color 0.16s ease,
    opacity 0.16s ease,
    background-color 0.16s ease;
}
.fleche svg {
  width: 20px;
  height: 20px;
}
.fleche:hover:not(:disabled) {
  border-color: var(--ink);
  background: var(--surface);
}
/* Au bout de la rangée, la flèche s'éteint au lieu de disparaître : un bouton
   qui s'en va déplace son voisin, et on clique à côté. */
.fleche:disabled {
  opacity: 0.3;
  cursor: default;
}
/* L'anneau du navigateur est bleu et carré : il jurait sur une pastille ronde.
   Le nôtre suit la forme. */
.fleche:focus-visible,
.point:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}

.tasks-rail {
  /* La barre de défilement disparaît : les deux flèches disent déjà qu'on peut
     pousser, et une barre horizontale au milieu d'une page qui défile
     verticalement est un objet de plus à comprendre. Le geste de défilement,
     lui, marche toujours, à la molette comme au doigt. */
  scrollbar-width: none;
  overflow-x: auto;
  /* La carte s'arrête AU MILIEU de l'écran, pas là où le geste s'est terminé :
     sans ancrage, celle qui grossissait était simplement la plus proche du
     centre, donc décalée d'une demi-carte une fois sur deux. */
  scroll-snap-type: x mandatory;
  overflow-y: hidden;
  /* Place calculée, pas estimée. Au survol la carte grossit de 6 % (soit 9,6 px
     de chaque côté sur 320 de haut) et prend `--shadow-raised`, dont la portée
     vaut décalage + flou : 32 - 12 = 20 px vers le haut, 12 + 32 = 44 px vers
     le bas. Il faut donc 30 au-dessus et 54 en dessous. Mes 26 / 34 précédents
     tranchaient l'ombre basse, et c'est elle qu'on voyait coupée. */
  padding: 34px 0 58px;
  /* PAS de magnétisme de défilement. `scroll-snap` accrochait la rangée à
     chaque carte : au doigt comme à la molette, elle partait puis se recalait,
     et ça se lisait comme des sauts. Une rangée qu'on pousse doit filer. */
  -webkit-overflow-scrolling: touch;
}
.tasks-rail::-webkit-scrollbar {
  display: none;
}

.tasks {
  display: flex;
  gap: 18px;
  /* La première carte s'aligne sur la colonne de contenu. */
  padding-left: var(--gouttiere-rail);
  margin: 0;
  list-style: none;
  width: max-content;
}
/* La marge de FIN est une case vide, pas un padding : le padding de fin d'un
   conteneur flex qui déborde n'est pas restitué partout, et la dernière carte
   se retrouvait collée au bord de l'écran. Une case, elle, fait partie de la
   rangée : elle est toujours là, quel que soit le thème affiché. */
.tasks::after {
  content: '';
  /* Moins l'écart entre cartes, qui s'applique AUSSI avant cette case : sans
     la soustraction, la fin respirait 18 px de plus que le début, et les deux
     bouts n'étaient pas symétriques. */
  flex: 0 0 calc(var(--gouttiere-rail) - 18px);
}
.task {
  flex: 0 0 auto;
  scroll-snap-align: center;
  /* Le CARRÉ, c'est la tâche : le visuel plus son nom. La ligne de rythme vient
     s'ajouter dessous, elle ne compte pas dans le calcul. Le visuel prend donc
     ce que le nom lui laisse à l'intérieur du carré. */
  --carre: 300px;
  /* DEUX lignes de nom : 2 x 20 px d'interligne, plus 26 de padding. Hauteur
     FIXE et pas minimale : avec `box-sizing: border-box`, un `min-height: 3em`
     valait 48 px padding compris, soit MOINS qu'un nom sur deux lignes. Le
     carré n'était donc pas le même d'une carte à l'autre. */
  --zone-nom: 66px;
  width: var(--carre);
  display: flex;
  flex-direction: column;
  background: var(--card);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow);
  overflow: hidden;
  transition:
    transform 0.18s ease,
    box-shadow 0.18s ease;
}
/* `display: flex` sur la carte l'emporte sur le `display: none` que le
   navigateur applique à l'attribut `hidden` : sans cette règle, les cartes des
   autres thèmes restaient invisibles MAIS occupaient la rangée, qui faisait
   33 000 px de large pour seize cartes à montrer. */
.task[hidden] {
  display: none;
}
.task:hover,
/* La tâche qui passe au MILIEU de l'écran grossit toute seule : c'est elle
   qu'on regarde, la rangée tourne autour. Même geste que le survol, pour ne pas
   avoir deux façons de dire « celle-ci ». */
.task.au-centre {
  transform: scale(1.06);
  box-shadow: var(--shadow-raised);
}
.task img {
  display: block;
  width: 100%;
  height: auto;
  /* Ce que le nom laisse dans le carré. `contain` pose le visuel entier,
     quelle que soit la hauteur qu'on lui donne. */
  flex: 0 0 auto;
  height: calc(var(--carre) - var(--zone-nom));
  object-fit: contain;
  padding: 16px 16px 0;
}
.task h3 {
  /* Hauteur de DEUX lignes, fixe : un libellé court ne doit pas remonter le
     filet, sinon la rangée ondule d'une carte à l'autre. */
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  height: var(--zone-nom);
  margin: 0;
  padding: 12px 14px 14px;
  font-size: 1rem;
  line-height: 1.25;
  /* Moins gras que les titres de section : ce sont des noms d'objets, pas des
     promesses. À 800 la rangée entière criait. */
  font-weight: 600;
}
/* Le rythme sur SA propre ligne, sous un filet qui va d'un bord à l'autre :
   c'est la carte de tâche de l'application, à l'identique. Hauteur FIXE de deux
   lignes : « À chaque ménage » et « Seulement si un animal est signalé » ne
   font pas la même longueur, et sans ça le bas des cartes se décalait. */
.task .rythme {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  min-height: 3.2em;
  margin: auto 0 0;
  /* Padding bas plus grand que le haut : le texte remonte dans sa bande au
     lieu de s'asseoir sur le bord de la carte. */
  padding: 10px 12px 18px;
  border-top: 1px solid var(--hairline);
  color: var(--subtle);
  font-size: 0.86rem;
  line-height: 1.3;
}

/* ------------------------------------------------- passage d'une page à l'autre */

/* PAS de fondu entre Gestionnaire et Intervenant, et c'est voulu.
 *
 * On avait posé une transition de vue. Un fondu entre deux DOCUMENTS ne peut
 * commencer qu'une fois la nouvelle page reçue : tant qu'elle arrive, l'ancienne
 * reste figée, puis tout s'enchaîne d'un coup. On voit un à-coup, pas une
 * douceur, et le remède est pire que le mal.
 *
 * La fluidité vient d'ailleurs : la page d'en face est PRÉ-RENDUE au survol
 * (règles de spéculation, cf. `_render.ts`). Le clic tombe alors sur un
 * document déjà prêt, et le changement est instantané. Rien à animer.
 */

/* ------------------------------------------------------- apparition au défilement */

.reveal {
  transition:
    opacity 0.55s ease,
    transform 0.55s ease;
}
.reveal-off {
  opacity: 0;
  transform: translateY(18px);
}

/* « Réduire les animations » : on retire les DÉPLACEMENTS, on garde les
   fondus. Le réglage vise le mal des transports, que provoquent les grands
   glissements ; une opacité qui change ne déplace rien et reste lisible. Tout
   supprimer revenait à servir une page où les choses apparaissent d'un coup,
   ce qui est plus sec, pas plus doux. */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  .reveal-off {
    /* Le bloc se révèle sans monter. */
    transform: none;
  }
}

/* --------------------------------------------------------------- responsive */

@media (max-width: 1000px) {
  .hero-grid,
  .steps-grid,
  .split-grid {
    grid-template-columns: 1fr;
  }
  .split-media {
    order: -1;
  }
  .split-flip .split-text {
    order: 0;
  }
  /* Une VITRINE fait l'inverse : le texte d'abord, le téléphone en dernier.
     Le cadre déborde sa scène de trois cents pixels et compte sur le bord bas
     de la section pour le trancher ; placé en premier, il recouvrait les
     puces. Et un téléphone de 660 px posé avant le titre repoussait la
     promesse sous la ligne de flottaison. */
  .showcase .split-media {
    order: 0;
  }
  /* PAS D'EMPILEMENT pour les VITRINES sur mobile. En une colonne, une vitrine
     fait le texte PUIS le téléphone, donc plus d'un écran de haut : collée en
     haut, sa moitié basse serait devenue inatteignable. Elles défilent. */
  main > section.showcase {
    position: relative;
    min-height: 0;
  }
  /* LE HERO, lui, reste COLLÉ, comme sur grand écran : sa hauteur est bornée à
     l'écran et le cadre du téléphone s'y fait trancher par le bas. Sans cette
     borne il faisait un écran et demi, et sa moitié basse ne se voyait jamais.
  */
  .hero {
    height: calc(100svh - var(--header-h));
    min-height: 0;
    overflow: clip;
  }
  /* Et le texte revient à gauche : ferré à droite sur une colonne pleine
     largeur, il n'avait plus de cadre à côté de qui se ranger. */
  .showcase:not(.split-flip) .split-text {
    text-align: left;
  }
  .cards-3 {
    grid-template-columns: repeat(2, 1fr);
  }
  .footer-grid {
    grid-template-columns: 1fr 1fr;
  }
}

@media (max-width: 780px) {
  body {
    font-size: 16px;
  }

  .cards-2,
  .cards-3,
  .stats ul,
  .ticks-cols,
  .footer-grid {
    grid-template-columns: 1fr;
  }
  .actions .btn {
    flex: 1 1 auto;
  }
  .cycle .tab {
    padding-inline: 18px;
  }
  /* TOUT se centre sur téléphone : une colonne unique, pleine largeur, où un
     texte ferré à gauche sous un titre centré fait deux alignements pour une
     seule idée. Les vitrines le font déjà plus bas ; ici, les têtes de section,
     les blocs sans capture (le prix), la FAQ et l'appel final. */
  .section-head,
  .split-text,
  .final {
    text-align: center;
  }
  .split-text .lead,
  .final .lead {
    margin-inline: auto;
  }
}

/* ------------------------------------------------------------------ mobile */

/* Deux éléments que la version bureau n'a pas, masqués par défaut : la marque
   posée dans le hero, et la barre d'action du bas. */
.hero-mark,
.action-bar {
  display: none;
}

/*
 * Sur téléphone, la barre du haut ne garde QUE le sélecteur de public.
 *
 * La marque et l'action en sortent : la première descend dans le hero, où elle
 * s'en va au premier défilement comme le reste de la page ; la seconde se pose
 * en bas, à portée de pouce. Une barre qui suit la page doit porter ce qui sert
 * à naviguer, pas ce qui sert une fois.
 */

/* ------------------------------------------------ plateformes et PMS */

.integrations-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  gap: clamp(32px, 5vw, 72px);
}
/* Cadre a DROITE : la section entre dans l'alternance des vitrines au lieu de
   figer son cote. Le texte se ferre a droite, comme partout ou le cadre est de
   ce cote la, pour que les deux colonnes se rejoignent au milieu. */
.integrations-flip .orbit {
  order: 2;
}
.integrations-flip .integrations-text {
  text-align: right;
}
.integrations-flip .integrations-text p {
  margin-left: auto;
}


/* ------------------------------------------------------------- la rangée */

/* Une RANGÉE : un carrousel d'arguments sur un écran. L'accroche occupe la
   première vue, seule ; ensuite un argument par vue, avec son rendu 3D, son
   titre et sa phrase, tous au MÊME endroit d'une vue à l'autre.

   Deux flèches et un point par vue en dessous. Le défilement de la page ne le
   pilote plus : il fallait cinq hauteurs d'écran pour traverser cinq
   arguments, c'était long pour ce que ça dit. */
.rail {
  padding: 0;
}
/* La section et sa scène s'effacent de la mise en page : leurs DEUX PAGES
   deviennent des enfants directs de `main`, exactement comme les vitrines.
   C'est ce qui leur donne la même portée de collage : sans ça, elles se
   décollaient à la fin de la section et la vitrine suivante arrivait par en
   dessous au lieu de passer par-dessus. */
main > section.rail,
.rail-scene {
  display: contents;
}
.rail-scene {
  overflow: clip;
}

/* DEUX pages pleines qui S'EMPILENT, comme les vitrines : chacune se cale en
   haut de l'écran et la suivante passe par-dessus. Partagées sur un seul écran,
   l'accroche mangeait la moitié de la hauteur et le bloc du milieu se
   retrouvait à l'étroit. */
.rail-head,
.rail-vue {
  position: sticky;
  top: 0;
  /* Le même fond que le hero et les vitrines : c'est lui qui recouvre la page
     précédente, et il fait de ces deux pages des pleines pages comme les
     autres, pas deux aplats blancs au milieu du parcours. */
  background: var(--fond-degrade);
  min-height: 100svh;
  /* La barre du haut est réservée : sans ça, le contenu passait dessous. */
  padding-top: calc(var(--header-h) + clamp(16px, 3vh, 40px));
}
.rail-head {
  display: grid;
  place-items: center;
  /* Pleine largeur : le fond opaque est ce qui recouvre la page précédente, et
     limité à la colonne de contenu il laissait deux bandes grises sur les
     côtés. La largeur de lecture est portée par le titre, pas par le bloc. */
  width: 100%;
  text-align: center;
  padding-inline: var(--gouttiere-rail);
  padding-bottom: calc(var(--header-h) + clamp(16px, 3vh, 40px));
}
.rail-vue {
  display: grid;
  /* Le visuel, le texte ET la navigation forment UN bloc, centré ensemble sur
     la hauteur. Avec une première rangée en 1fr, les flèches étaient plaquées
     en bas de l'écran, loin de la phrase qu'elles font défiler. */
  grid-template-rows: auto auto;
  align-content: center;
  justify-items: center;
  gap: clamp(16px, 2.4vh, 34px);
  padding-bottom: clamp(28px, 4vh, 56px);
}
.rail-track {
  width: 100%;
}
.rail-head h2 {
  max-width: 900px;
  margin-inline: auto;
}

.rail-track {
  display: grid;
  /* Le bloc se centre dans la place que lui laissent l'accroche et les flèches.
     Calé en haut, il laissait tout le vide d'un grand écran sous lui. Sa
     hauteur étant fixe, le titre et la phrase restent au même endroit d'une vue
     à l'autre. */
  align-content: center;
}
.rail-track > li {
  grid-area: 1 / 1;
  display: grid;
  justify-items: center;
  /* Toutes les vues occupent LA MÊME case de grille : la rangée prend d'office
     la hauteur de la plus haute, et chaque vue s'y étire, contenu calé en haut.
     Le visuel, le titre et la phrase tombent donc au même endroit d'une vue à
     l'autre, sans hauteur fixe qui laisserait un grand vide sous les courtes. */
  align-content: start;
  text-align: center;
  padding-inline: 24px;
  opacity: 0;
  translate: 0 22px;
  transition:
    opacity 0.45s ease,
    translate 0.45s ease;
  /* Les vues sont empilées sur la même case de grille : celles qu'on ne voit
     pas doivent laisser passer le clic, sinon c'est la dernière du lot qui
     l'attrape et le visuel affiché ne réagit jamais. */
  pointer-events: none;
}
.rail-track > li.on {
  opacity: 1;
  translate: 0 0;
  pointer-events: auto;
}
.rail-track .art {
  margin: 0 0 clamp(24px, 3vh, 44px);
}
/* Le visuel avance d'une vue au clic. */
.rail-track .cliquable {
  cursor: pointer;
}
.rail-track .art img {
  width: clamp(160px, 18vw, 250px);
  height: auto;
}
.rail-track > li h3 {
  font-size: clamp(1.6rem, 3vw, 2.6rem);
}
.rail-texte {
  /* La mesure de lecture s'applique a la PHRASE, pas au titre : a 450px, un
     titre comme « Assignation en un geste » cassait en deux lignes alors qu'il
     tient largement sur une, et la place ne manque pas. */
  max-width: 100%;
}
.rail-track > li h3 {
  text-wrap: balance;
}
.rail-track p {
  max-width: var(--mesure);
  margin-inline: auto;
  margin-top: 16px;
  color: var(--ink);
  font-weight: 500;
  font-size: clamp(1.02rem, 1.3vw, 1.18rem);
}

.rail-nav {
  display: flex;
  align-items: center;
  gap: 10px;
}
.rail-points {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-inline: 4px;
}
.point {
  display: block;
  width: 9px;
  height: 9px;
  padding: 0;
  border: 0;
  border-radius: var(--pill);
  background: color-mix(in srgb, var(--ink) 22%, transparent);
  cursor: pointer;
  transition:
    background-color 0.2s ease,
    width 0.2s ease;
}
/* Le point courant s'ALLONGE au lieu de changer seulement de couleur : à neuf
   pixels, une nuance de gris ne se distingue pas d'un coup d'oeil. */
.point-on {
  width: 26px;
  background: var(--ink);
}

@media (max-width: 900px) {
  /* Sur mobile la rangée redevient une pile : un carrousel de six vues demande
     six gestes, alors qu'un pouce descend tout seul. */
  .rail {
    padding: var(--gap-section) 0;
  }
  /* La section reprend sa PLACE dans la mise en page : effacée (`contents`),
     son rembourrage ne s'appliquait pas et l'accroche se retrouvait écrasée
     entre deux vitrines, sans un pixel d'air. L'empilement qu'on lui enlève
     ici n'existe de toute façon que sur grand écran. */
  /* La section reste EFFACÉE de la mise en page (`display: contents`, hérité du
     grand écran) : c'est ce qui donne à l'accroche la portée de collage de
     `main`, donc une vitrine qui lui passe dessus au lieu de la pousser. */
  .rail-scene {
    min-height: 0;
    overflow: visible;
    padding-bottom: 0;
  }
  .rail-head,
  .rail-vue {
    position: relative;
    min-height: 0;
    padding-top: 0;
    padding-bottom: 0;
    display: block;
  }
  /* L'accroche est une PAGE COLLÉE, comme sur grand écran : elle tient l'écran
     entier et la vitrine suivante lui passe par-dessus. Tassée entre deux
     sections, elle passait pour un titre de paragraphe. Écrite APRÈS la règle
     qui remet les deux pages à plat : à spécificité égale, c'est l'ordre qui
     tranche. */
  .rail {
    padding: 0;
  }
  .rail-head {
    position: sticky;
    top: var(--header-h);
    display: grid;
    place-items: center;
    min-height: calc(100svh - var(--header-h));
    /* Le bas reserve la barre d'action : sans elle dans le calcul, le titre
       se centrait sur la page entiere et tombait quarante pixels trop bas. */
    padding: 0 24px var(--barre-bas);
    text-align: center;
  }
  .rail-track > li {
    opacity: 1;
    translate: none;
  }
  .rail-track {
    display: grid;
    gap: clamp(48px, 8vh, 88px);
    margin-top: clamp(40px, 6vh, 72px);
  }
  .rail-track > li {
    grid-area: auto;
    height: auto;
    /* Les vues sont toutes affichées à la suite : elles reprennent la main sur
       le pointeur, et le visuel n'a plus rien à annoncer au clic. */
    pointer-events: auto;
  }
  .rail-track .cliquable {
    cursor: default;
  }
  .rail-track .art img {
    width: clamp(150px, 42vw, 220px);
  }
  .rail-nav {
    display: none;
  }
}

/* -------------------------------------------------------------- vitrines */

/* Une VITRINE : une capture de l'app dans un téléphone, posée sur le dégradé
   du hero, coupée par le bas de la section, et qui dérive au défilement.
   Quatre sections d'affilée la partagent, le téléphone alternant gauche et
   droite. C'est le fil de la page : on descend, et l'application se montre
   écran après écran au lieu de se raconter.

   Toutes les règles sont ici et pas dans chaque section : `integrations` et
   les trois `split` à capture ne diffèrent que par leur contenu. */
.showcase {
  background: var(--fond-degrade);
  /* Le cadre descend jusqu'au bord et s'y fait trancher, comme dans le hero.
     Et RIEN en haut : le téléphone est calé en bas de toute façon, alors qu'un
     retrait en haut décalait le centre du texte de trente-cinq pixels vers le
     bas. Sans lui, la colonne de texte se centre sur la page. */
  padding-top: 0;
  padding-bottom: 0;
  /* `clip` et PAS `hidden` : `hidden` ferait de la section un conteneur de
     défilement, ce qui casse le collage. `clip` coupe pareil sans jamais
     devenir un scroller. */
  overflow: clip;

  /* L'EMPILEMENT. Chaque vitrine se cale en haut de l'écran et n'en bouge
     plus ; la suivante monte et passe par-dessus, comme des cartes qu'on pose
     l'une sur l'autre. C'est le fond opaque de chacune qui recouvre la
     précédente, et le `z-index` croissant de l'ordre du document qui décide
     laquelle est devant.

     Une hauteur d'écran au minimum : en dessous, deux vitrines auraient été
     visibles en même temps et le recouvrement n'aurait plus rien recouvert. */
  position: sticky;
  top: 0;
  min-height: 100svh;
  /* Le contenu est poussé EN BAS, pour que le téléphone touche toujours le
     bord de l'écran et s'y fasse trancher, quelle que soit la hauteur. */
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}
/* Le cadre part du HAUT, à la même hauteur que celui du hero, et descend
   jusqu'au bord bas qui le tranche. Calé sur le bas, il tombait d'autant plus
   bas que l'écran était haut : sur un grand écran, le téléphone d'une vitrine
   se retrouvait deux cents pixels sous celui du hero. */
.showcase:not(.showcase-radar) .split-media,
.showcase .orbit {
  align-self: start;
  margin-top: var(--tel-haut);
}
/* Le radar n'a pas de cadre à trancher : il se centre sur la hauteur, comme son
   texte. La remontée ci-dessus l'aurait poussé de cent cinquante pixels. */
.showcase-radar .split-media {
  align-self: center;
  margin-top: 0;
}
/* La règle générale (`main > section:not(.hero)`) repositionne TOUTES les
   sections en `relative` et l'emporte par sa spécificité : sans ce rappel, la
   pile ne collait pas. Ce qui n'est pas une vitrine garde son `z-index: 1` et
   passe donc bien par-dessus la pile, ce qu'on veut pour la bibliothèque de
   tâches qui la suit. */
main > section.showcase {
  position: sticky;
}
/* Posée le temps d'une mesure par le script de dérive : `offsetTop` d'un
   élément collé renvoie sa position décalée, pas sa position de flux. Les deux
   pages de la rangée collent aussi : elles se décollent avec le reste. */
.sans-collage main > section.showcase,
.sans-collage .rail-head,
.sans-collage .rail-vue {
  position: relative;
}
.showcase .split-media,
.showcase .orbit {
  align-self: end;
}

/* ---- la mise en relation : une photo de logement, cerclée d'ondes ---- */

/* Cette vitrine-là n'a pas de téléphone à trancher : la photo se centre, et la
   section retrouve son fond de page. */
.showcase-radar {
  /* Rien à trancher en bas, donc rien à coller au bord : le bloc se centre dans
     l'écran. Poussé vers le bas comme les autres, il laissait un tiers de vide
     sous lui. Les deux retraits sont ÉGAUX, sinon le centre se décalait de la
     moitié de leur écart. */
  padding-block: var(--gap-section);
  justify-content: center;
}
/* La marge basse sert à décoller le TÉLÉPHONE du bord ; ici elle ne faisait que
   décentrer le bloc de soixante pixels vers le haut. */
.showcase-radar .wrap {
  margin-bottom: 0;
}
.showcase-radar .split-media {
  align-self: center;
}

.radar {
  position: relative;
  display: grid;
  place-items: center;
  margin: 0 auto;
  width: min(100%, 460px);
  aspect-ratio: 1;
  /* La dérive, en plus petit que le téléphone : c'est un point sur une carte,
     pas un objet posé devant. */
  translate: 0 calc(var(--p, 0) * -10px);
}
.radar img {
  position: relative;
  z-index: 1;
  width: 46%;
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: 50%;
  box-shadow: var(--shadow-raised);
}

/* Le halo fixe, puis trois ondes qui partent de la photo et se perdent. Trois
   et pas une : une seule onde fait un clignotement, trois font une recherche
   qui continue. Décalées d'un tiers de cycle, pour qu'il y en ait toujours une
   en route. */
.ondes {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: radial-gradient(
    circle,
    color-mix(in srgb, var(--accent) 13%, transparent) 0%,
    transparent 68%
  );
}
.ondes i {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 1.5px solid color-mix(in srgb, var(--accent) 42%, transparent);
  opacity: 0;
  animation: onde 3.6s cubic-bezier(0.22, 0.61, 0.36, 1) infinite;
}
.ondes i:nth-child(2) {
  animation-delay: 1.2s;
}
.ondes i:nth-child(3) {
  animation-delay: 2.4s;
}
@keyframes onde {
  0% {
    transform: scale(0.46);
    opacity: 0;
  }
  10% {
    opacity: 0.8;
  }
  100% {
    transform: scale(1);
    opacity: 0;
  }
}

/* L'icône de l'application, posée de travers au-dessus du cadre, comme les
   icônes des plateformes autour du téléphone des connexions. Elle dérive plus
   vite que lui et dans l'autre sens : c'est l'écart entre les deux qui la
   décolle du cadre au lieu de la coller dessus. */
.marque-app {
  position: absolute;
  z-index: 2;
  /* En bas a droite du cadre, a cheval sur son bord : posee en haut a gauche,
     elle tombait dans la colonne de texte et se lisait comme un logo de page. */
  bottom: clamp(60px, 12vh, 150px);
  right: -34px;
  width: clamp(76px, 7vw, 112px);
  height: auto;
  border-radius: 24px;
  box-shadow: var(--shadow-flottant);
  rotate: 9deg;
  translate: 0 calc(var(--p, 0) * 26px);
}
/* Le cadre du hero est à droite : l'icône se pose sur son bord gauche, du côté
   du texte, là où l'oeil arrive. */
.hero-media {
  position: relative;
}

/* La SCÈNE donne la hauteur ; le téléphone la déborde par le bas et se fait
   couper par le bord de la section. Sans elle, le cadre entier entrait dans le
   flux et une section faisait deux écrans de haut pour trois phrases. */
.phone-stage {
  position: relative;
  /* Du haut du cadre jusqu'au bord bas de l'écran, qui le tranche. */
  height: calc(100svh - var(--tel-haut));
}
.phone-stage .phone-screen {
  /* La taille du téléphone du hero, à l'identique. */
  width: min(100%, 470px);
}

/* La DÉRIVE. Le téléphone monte plus lentement que la page ; les icônes
   d'application, elles, bougent à peine et dans l'AUTRE sens. Deux plans qui ne
   suivent pas le même rythme, c'est ce qui fait la profondeur : les faire
   glisser ensemble n'aurait fait que déplacer un bloc.

   `--p` va de -1 (la section entre par le bas) à +1 (elle sort par le haut), et
   vient du script de dérive. Pas de `animation-timeline: view()` : Chrome sait
   le faire, mais l'animation ne tourne pas dans un onglet caché et le reste du
   parc ne la connaît pas encore. Une propriété posée au défilement marche
   partout et se mesure.

   `translate` plutôt que `transform` : les icônes portent déjà un `rotate`, et
   ces deux propriétés-là se composent sans s'écraser l'une l'autre. */
.showcase .phone {
  /* Douze pixels. La vitrine est collée en haut de l'écran, immobile ; ce qui
     bouge à l'intérieur doit rester de l'ordre du souffle, sinon on croit que
     le collage a lâché. */
  translate: 0 calc(var(--p, 0) * -12px);
  will-change: translate;
}
.showcase .orbit-logos li {
  /* Dix-huit pixels, dans l'AUTRE sens : les icônes flottent autour du cadre au
     lieu de l'accompagner. Deux vitesses opposées, c'est ce qui rend la scène
     vivante ; à la même vitesse elles auraient juste été collées dessus. */
  translate: 0 calc(var(--p, 0) * 18px);
}

.orbit {
  position: relative;
}

/* Les plateformes gravitent autour du cadre, chacune un peu de travers. Posées
   droites, elles refaisaient une grille ; inclinées et décalées, elles disent
   qu'elles tournent autour du produit. */
.orbit-logos {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.orbit-logos li {
  position: absolute;
  display: block;
}
/* L'icône PLEINE, sans pastille blanche autour : ce sont déjà des icônes
   d'application, carrées et colorées. Les poser dans un cadre blanc revenait à
   encadrer un cadre. L'ombre porte l'icône elle-même. */
.orbit-logos img {
  display: block;
  width: 96px;
  height: 96px;
  object-fit: cover;
  border-radius: 22px;
  box-shadow: var(--shadow-flottant);
}
/* Positions et angles ecrits a la main : une rotation calculee aurait donne six
   fois le meme geste. Trois logos a GAUCHE, trois a DROITE, et les deux
   colonnes se decalent en quinconce : a hauteur egale ils se seraient regardes
   deux par deux, et a moins d'un tiers d'ecart ils se touchaient. */
.orbit-1 {
  top: 4%;
  left: -2%;
  rotate: -9deg;
}
.orbit-2 {
  top: 46%;
  right: -4%;
  rotate: 7deg;
}
.orbit-3 {
  top: 70%;
  left: 2%;
  rotate: 5deg;
}
.orbit-4 {
  top: 80%;
  right: 4%;
  rotate: -6deg;
}
.orbit-5 {
  top: 37%;
  left: -6%;
  rotate: 8deg;
}
.orbit-6 {
  top: 13%;
  right: 6%;
  rotate: -5deg;
}
/* A quatre, les deux dernieres positions n'existent pas : la quatrieme reprend
   la place basse d'origine, sinon les logos de plateformes se retrouvaient
   tasses en bas de cadre. */
.orbit-logos:not(:has(li:nth-child(5))) .orbit-2 {
  top: 30%;
}
.orbit-logos:not(:has(li:nth-child(5))) .orbit-3 {
  top: 58%;
}
.orbit-logos:not(:has(li:nth-child(5))) .orbit-4 {
  top: 76%;
}
/* A six, les icones se serrent un peu, mais a peine : elles portent des marques
   qu'on doit reconnaitre au premier coup d'oeil. */
.orbit-logos:has(li:nth-child(5)) img {
  width: 90px;
  height: 90px;
  border-radius: 21px;
}

.integrations-text h3 {
  margin-top: clamp(28px, 4vw, 44px);
  font-size: clamp(1.4rem, 2.2vw, 1.9rem);
}
.integrations-text p {
  max-width: calc(var(--mesure) + 50px);
  color: var(--ink);
  font-weight: 500;
}
.integrations-text .lead {
  margin-top: 20px;
  font-size: clamp(1.1rem, 1.4vw, 1.3rem);
}
.integrations-text h3 + p {
  margin-top: 12px;
  font-size: 1.05rem;
}

@media (max-width: 900px) {
  .integrations-grid {
    grid-template-columns: 1fr;
  }
  .integrations-flip .integrations-text {
    text-align: left;
  }
  .integrations-flip .integrations-text p {
    margin-left: 0;
  }
  .orbit {
    order: 2;
  }
  /* Le téléphone rétrécit avec la colonne, pas les icônes : à 96 px elles
     mangeaient l'écran et sortaient du cadre.

     UNE SEULE TAILLE pour les trois familles (plateformes de réservation,
     PMS, boutiques d'application) : elles se suivent d'une section à l'autre
     sur le même cadre de téléphone, et deux tailles se lisaient comme deux
     niveaux d'importance. Le sélecteur `:has()` est répété parce qu'il pèse
     plus lourd que `.orbit-logos img`, et qu'une media query n'ajoute AUCUNE
     spécificité : sans cette ligne, les six PMS restaient plus gros. */
  .orbit-logos img,
  .orbit-logos:has(li:nth-child(5)) img {
    width: 78px;
    height: 78px;
    border-radius: 18px;
  }
  .orbit-1 {
    left: 2%;
  }
  .orbit-2 {
    right: 0;
  }
  .orbit-3 {
    left: 6%;
  }
  .orbit-4 {
    right: 6%;
  }
}

/* Les PASTILLES posees sur un cadre : les deux boutiques, sur la vitrine qui
   promet l'application des deux cotes. Meme geste que les plateformes autour du
   telephone des connexions, en deux exemplaires seulement. */
.split-media {
  position: relative;
}
.badges {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.badges li {
  position: absolute;
  display: block;
}
.badges img {
  display: block;
  width: clamp(64px, 7vw, 92px);
  height: auto;
  border-radius: 22px;
  box-shadow: var(--shadow-flottant);
}
/* Sur telephone, les deux boutiques prennent la taille des plateformes et des
   PMS. Regle posee APRES `.badges img` et non dans le bloc mobile plus haut :
   a specificite egale, c'est la derniere declaration qui l'emporte, et celle
   d'origine venait apres. */
@media (max-width: 900px) {
  .badges img {
    width: 78px;
    height: 78px;
    border-radius: 18px;
  }
}

/* Le logo de Google Play est dessine pour un fond clair : il lui faut sa
   pastille blanche, et de l'air autour. */
.badge-fond img {
  background: #fff;
  padding: 14%;
  box-sizing: border-box;
}
.badge-1 {
  top: 12%;
  left: -4%;
  rotate: -8deg;
}
.badge-2 {
  top: 38%;
  right: -3%;
  rotate: 7deg;
}
/* Elles derivent comme les plateformes : a l'envers du cadre, plus doucement. */
.showcase .badges li {
  translate: 0 calc(var(--p, 0) * 18px);
}

/* La COUTURE. Une section qui défile par-dessus les pages collées démarre à un
   pixel fractionnaire : il restait une ligne transparente par laquelle on
   voyait la capture d'en dessous. Elle mord donc d'un pixel sur la précédente.
*/
main > section:not(.showcase):not(.rail):not(.hero) {
  /* `relative` sert deux fois : fermer la couture, et servir de repere au
     filet du bord haut (voir plus bas). */
  position: relative;
  z-index: 1;
  margin-top: -1px;
}

/* Le FILET de la page qui arrive : un cheveu gris sur son bord haut, pose
   par-dessus la page precedente pendant qu'elle monte, efface des qu'elle vient
   se caler sous la barre (le script publie `--filet`). Sans lui, deux degrades
   identiques se rejoignaient sans qu'on voie la couture ; en permanence, il
   laissait une ligne en travers de l'ecran. */
main > section:not(.hero)::before,
.rail-head::before,
.rail-vue::before {
  content: '';
  position: absolute;
  inset: 0 0 auto;
  height: 1px;
  background: var(--hairline);
  opacity: var(--filet, 0);
  pointer-events: none;
  z-index: 2;
}

/* -------------------------------------------------------- menu de téléphone */

/* Rien sur grand écran : la barre y porte déjà la marque, le sélecteur de
   public et l'action. */
.burger {
  display: none;
}
.burger-bouton {
  position: relative;
  /* Au-dessus du panneau : c'est lui qui le referme. */
  z-index: 80;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
  /* Sans ça, le navigateur dessine son triangle de dépliage. */
  list-style: none;
}
.burger-bouton::-webkit-details-marker {
  display: none;
}
.burger-bouton svg {
  grid-area: 1 / 1;
  width: 22px;
  height: 22px;
}
/* Trois traits fermé, une croix ouvert : le même bouton fait les deux gestes,
   au même endroit. */
.burger:not([open]) .burger-croix,
.burger[open] .burger-traits {
  display: none;
}
.burger[open] .burger-bouton {
  background: var(--ink);
  color: #fff;
}

/* Le panneau prend TOUT l'écran. Un menu de quatre rubriques dans une vignette
   de deux cents pixels ne valait pas le geste ; en pleine page, les rubriques
   se lisent de loin et la marque signe le bas. */
.burger-menu {
  position: fixed;
  inset: 0;
  z-index: 70;
  display: grid;
  grid-template-rows: 1fr auto;
  justify-items: center;
  background: var(--canvas);
  /* Le bas dégage la barre d'action : la marque tombait juste derrière elle. */
  padding: calc(var(--header-h) + 24px) 24px calc(var(--barre-bas, 96px) + 24px);
}
/* La barre du haut passe DEVANT tout pendant que le menu est ouvert : son
   panneau vit dedans, et sans ça il restait sous la barre d'action du bas, qui
   est plus haute dans la pile. */
.site-header:has(.burger[open]) {
  z-index: 90;
}
.burger-liens {
  display: grid;
  align-content: center;
  justify-items: center;
  gap: 6px;
}
.burger-menu a {
  padding: 10px 4px;
  font-size: clamp(2rem, 9vw, 2.6rem);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.1;
}
.burger-menu a[aria-current='page'] {
  color: var(--accent);
}
.burger-logo {
  width: 168px;
  height: auto;
}

/* La COUTURE. Une section qui défile par-dessus les pages collées démarre à un
   pixel fractionnaire : il restait une ligne transparente par laquelle on
   voyait la capture d'en dessous. Elle mord donc d'un pixel sur la précédente.
*/
main > section:not(.showcase):not(.rail):not(.hero) {
  /* `relative` sert deux fois : fermer la couture, et servir de repere au
     filet du bord haut (voir plus bas). */
  position: relative;
  z-index: 1;
  margin-top: -1px;
}

/* Le FILET de la page qui arrive : un cheveu gris sur son bord haut, pose
   par-dessus la page precedente pendant qu'elle monte, efface des qu'elle vient
   se caler sous la barre (le script publie `--filet`). Sans lui, deux degrades
   identiques se rejoignaient sans qu'on voie la couture ; en permanence, il
   laissait une ligne en travers de l'ecran. */
main > section:not(.hero)::before,
.rail-head::before,
.rail-vue::before {
  content: '';
  position: absolute;
  inset: 0 0 auto;
  height: 1px;
  background: var(--hairline);
  opacity: var(--filet, 0);
  pointer-events: none;
  z-index: 2;
}

/* ------------------------------------------------------------------ mobile */

/* Deux éléments que la version bureau n'a pas, masqués par défaut : la marque
   posée dans le hero, et la barre d'action du bas. */
.hero-mark,
.action-bar {
  display: none;
}

/*
 * Sur téléphone, la barre du haut ne garde QUE le sélecteur de public.
 *
 * La marque et l'action en sortent : la première descend dans le hero, où elle
 * s'en va au premier défilement comme le reste de la page ; la seconde se pose
 * en bas, à portée de pouce. Une barre qui suit la page doit porter ce qui sert
 * à naviguer, pas ce qui sert une fois.
 */
@media (max-width: 780px) {
  :root {
    /* La barre ne porte plus qu'un rond de menu : sa hauteur RÉELLE, pas les
       cent pixels du bureau. Tout ce qui s'accroche à ce jeton remonte
       d'autant, le hero comme les pages collées. */
    --header-h: 62px;
    /* La barre d'action du bas : sa hauteur réelle, encoche comprise. Tout ce
       qui finit en bas d'une page s'arrête au-dessus. */
    --barre-bas: calc(78px + env(safe-area-inset-bottom));
  }
  .site-header .brand,
  .header-inner > .btn,
  /* Le sélecteur de public part dans le menu : il tenait la barre à lui seul et
     poussait tout le reste dehors. */
  .tabs {
    display: none;
  }
  .burger {
    display: block;
    position: relative;
  }
  /* AUCUN FOND derrière les onglets tant qu'elles flottent sur le hero ou sur
     une vitrine : ce sont des dégradés, et un aplat blanc flou en travers
     n'aurait fait que souligner deux pastilles qui tiennent très bien toutes
     seules.
     Le fond REVIENT dès qu'autre chose passe dessous (`scrolled`, posé par le
     script quand ce n'est plus une pleine page) : sans lui, les questions de la
     FAQ et les tâches du catalogue traversaient les pastilles. */
  .site-header {
    background: transparent;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    box-shadow: none;
  }
  /* Et RIEN non plus quand on defile : la barre ne porte plus qu'un rond, qui
     a son propre fond. Un aplat blanc flou en travers de l'ecran se lisait
     comme un bandeau pose sur la page. */
  .header-inner {
    grid-template-columns: 1fr;
    /* Le rond du menu, seul, à DROITE, avec l'écart des croix de l'app. */
    justify-items: end;
    height: auto;
    padding: 20px 20px 0;
  }

  /* La marque, au-dessus du titre, centrée, dans le flux : les deux étincelles
     SEULES, sans le carré de l'icône. Posée sur le dégradé du hero, une
     pastille blanche à coins arrondis faisait une vignette d'application au
     milieu d'une page qui n'en est pas une. */
  .hero-mark {
    display: block;
    width: 48px;
    height: auto;
    margin: 0 auto 12px;
  }
  /* Le hero démarre juste sous les onglets : le rembourrage du bureau ajoutait
     trente-deux pixels à une page qui n'en a pas à perdre. */
  .hero {
    --hero-pad: 4px;
  }
  /* L'icône de l'application posée sur le cadre du hero s'en va : à cette
     taille elle mange un coin de la capture, qui est la seule chose à voir. */
  .marque-app {
    display: none;
  }
  /* Le téléphone des vitrines remonte : il gardait la réserve prévue pour la
     barre du bureau, soit cent cinquante pixels de vide au-dessus de lui, sur
     un écran qui en fait huit cents. */
  /* Le texte des vitrines se CENTRE : en une colonne, un bloc ferré à gauche
     sous une capture centrée fait deux alignements pour une seule idée. */
  .showcase .split-text,
  .showcase:not(.split-flip) .split-text,
  .showcase .integrations-text {
    /* Le `:not(.split-flip)` est repris tel quel : la règle qui ferre le texte
       à gauche l'emporte sinon, une requête de media n'ajoutant pas de
       spécificité. */
    text-align: center;
  }
  .showcase .split-text .lead,
  .showcase .integrations-text p {
    margin-inline: auto;
    text-align: center;
  }
  /* Et de l'air AU-DESSUS du titre : la capture de la vitrine précédente est
     tranchée par le bord de sa section, et le titre suivant lui arrivait
     dessus. */
  main > section.showcase {
    padding-top: clamp(96px, 22vw, 132px);
    /* Le bas de la vitrine s'arrête AU-DESSUS de la barre d'action, et de vingt
       pixels de plus : le cadre du téléphone finissait pile derrière le bouton,
       et on le lisait comme tranché. */
    padding-bottom: calc(var(--barre-bas) + 24px);
  }
  .hero,
  main > section:last-of-type {
    padding-bottom: var(--barre-bas);
  }
  /* Et de l'air SOUS le texte, avant la capture : à trente pixels, la phrase
     et le cadre se touchaient presque, et l'oeil lisait un seul bloc. */
  .showcase .split-grid,
  .showcase .integrations-grid {
    gap: clamp(56px, 14vw, 88px);
  }
  /* Le bloc SANS capture (le prix) : titre, puis le visuel, puis la phrase. Le
     texte se coupe en deux autour de l'image parce que `display: contents`
     promeut ses enfants en éléments de la grille ; le visuel se glisse entre le
     titre et la phrase. Les vitrines à téléphone gardent leur ordre : titre,
     phrase, capture. */
  .split:not(.showcase) .split-text {
    display: contents;
  }
  .split:not(.showcase) .split-media {
    order: 2;
  }
  .split:not(.showcase) h2 {
    order: 1;
  }
  .split:not(.showcase) .lead {
    order: 3;
    /* Serré : sous un visuel centré, une phrase pleine largeur se lisait comme
       un paragraphe de page, pas comme la légende d'une image. */
    max-width: 300px;
  }
  .split:not(.showcase) .actions {
    order: 4;
  }
  .split:not(.showcase) .split-grid {
    row-gap: 12px;
  }
  .split:not(.showcase) .art img {
    width: min(84%, 300px);
  }
  /* La phrase se colle au visuel : sa marge haute s'ajoutait à l'espace de la
     grille et faisait deux fois le même blanc. */
  .split:not(.showcase) .lead {
    margin-top: 0;
  }
  .showcase:not(.showcase-radar) .split-media,
  .showcase .orbit {
    /* MEME specificite que la regle du bureau : une requete de media n'en
       ajoute pas, et `.showcase:not(...) .split-media` l'emportait sur un
       simple `.showcase .split-media`. Le telephone gardait ses cent trente
       pixels de reserve. */
    margin-top: 0;
  }
  .phone-stage {
    height: auto;
  }
  /* Cadre plus ETROIT sur telephone : a pleine largeur, une capture de 390 sur
     844 fait deux fois la hauteur de l'ecran a elle seule. On perd un peu de
     largeur, on gagne beaucoup de defilement. */
  .hero-media .phone-screen,
  .phone-stage .phone-screen,
  .split-media .phone-screen {
    width: min(84%, 300px);
  }
  .hero-text {
    text-align: center;
    /* Le hero s'empile : le titre est AU-DESSUS du téléphone, pas à côté. La
       compensation de centrage n'a plus d'objet, elle ne ferait qu'un trou. */
    margin-bottom: 24px;
  }
  .hero h1 {
    /* Plus gros qu'en version bureau proportionnellement : sur un écran étroit,
       le titre est la seule chose à lire, il peut prendre toute la place. */
    font-size: clamp(2.9rem, 11vw, 4rem);
  }
  .hero .lead {
  max-width: var(--mesure);
    margin-inline: auto;
  }

  /* L'action, fixée en bas. Fond translucide et flou : le contenu qui passe
     dessous reste deviné, la barre ne coupe pas la page en deux. */
  .action-bar {
    display: block;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 60;
    padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
    background: rgba(255, 255, 255, 0.86);
    backdrop-filter: saturate(180%) blur(14px);
    -webkit-backdrop-filter: saturate(180%) blur(14px);
    /* Filet interne, comme l'en-tête : une bordure ajouterait à la hauteur. */
    box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.14);
  }
  .action-bar .btn {
    display: flex;
    width: 100%;
  }
  /* Arrivee sur le noir de fin : la barre s'efface. L'appel final porte deja le
     meme bouton, et elle couvrait les liens du pied et les pastilles des
     boutiques. */
  .au-pied .action-bar {
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.25s ease;
  }
  /* Le pied de page ne doit pas finir sous la barre. */
  .site-footer {
    padding-bottom: 96px;
  }

  /* Cartes de tâches un peu moins larges : à 300 px elles débordaient de
     l'écran et on ne voyait jamais la suivante. */
  .task {
    --carre: 232px;
  }
  /* Les pastilles de thème tiennent sur UNE ligne qui se pousse : à six sur
     deux rangées, elles faisaient un pavé de boutons avant la rangée de tâches,
     qui se pousse elle aussi. Le débord est coupé par le bord de l'écran, comme
     la rangée. */
  .themes {
    flex-wrap: nowrap;
    justify-content: flex-start;
    overflow-x: auto;
    scrollbar-width: none;
    /* La gouttière de la colonne est reprise en rembourrage : la première
       pastille reste alignée sur le texte, et la dernière ne colle pas au bord.
    */
    margin-inline: -24px;
    padding-inline: 24px;
    scroll-padding-inline: 24px;
  }
  .themes::-webkit-scrollbar {
    display: none;
  }
  .theme {
    white-space: nowrap;
  }
  /* Les flèches du catalogue s'en vont : au doigt, la rangée se pousse toute
     seule, et deux ronds vides sous elle n'occupaient que la place. */
  .catalogue-fleches {
    display: none;
  }
}

/* ------------------------------------------------ plateformes et PMS */

/* Deux volets, l'un sous l'autre : ce à quoi on se branche, et ce qu'on n'est
   pas. Colonne étroite et texte centré : ce sont deux affirmations, pas une
   liste de fonctions, et elles se lisent d'une traite. */
.integration {
  max-width: 720px;
  margin: 0 auto;
  text-align: center;
}
.integration + .integration {
  margin-top: clamp(56px, 7vw, 88px);
}
.integration h3 {
  font-size: clamp(1.5rem, 2.4vw, 2rem);
}
.integration p {
  margin: 16px auto 32px;
  color: var(--subtle);
  font-size: clamp(1.05rem, 1.3vw, 1.2rem);
  max-width: 60ch;
}
/* Les logos de PMS sont des signatures horizontales, pas des icônes carrées
   comme les favicons de plateformes : leur pastille s'élargit pour ne pas les
   écraser, et l'image garde ses proportions. */
.integration .logo-row li {
  width: auto;
  min-width: 76px;
  height: 76px;
  padding: 0 18px;
}
.integration .logo-row img {
  max-height: 40px;
  width: auto;
  object-fit: contain;
}

/* ------------------------------------------------------- consentement */
/* Le bandeau des traceurs. Posé en bas à gauche, pas en travers de l'écran :
   un voile modal qui bloque la lecture tant qu'on n'a pas répondu est une
   forme de pression, et la CNIL n'exige pas de bloquer le site.

   Il ne paraît que si un traceur est configuré (le HTML lui-même est vide
   sinon), et le script le démasque : sans JavaScript, aucun traceur ne se
   charge non plus, donc il n'y a rien à consentir. */
.consent {
  position: fixed;
  left: 20px;
  bottom: 20px;
  z-index: 60;
  max-width: 420px;
}

.consent[hidden] {
  display: none;
}

.consent-carte {
  background: var(--card);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-raised);
  padding: 24px;
}

.consent-carte h2 {
  font-size: 1.05rem;
  margin: 0 0 8px;
}

.consent-carte p {
  margin: 0;
  color: var(--subtle);
  font-size: 0.92rem;
  line-height: 1.5;
}

.consent-carte a {
  color: var(--accent);
  text-decoration: underline;
}

/* Les deux boutons partagent la largeur, à parts EGALES. Refuser doit coûter
   exactement le même geste qu'accepter : c'est la condition que la CNIL
   vérifie en premier, et un « refuser » deux fois plus petit suffit à faire
   tomber tout le reste. */
.consent-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-top: 18px;
}

.consent-actions .btn {
  padding: 13px 16px 15px;
  font-size: 0.95rem;
  width: 100%;
}

/* Un bouton qui doit se lire comme un lien : le pied de page n'a que des
   liens, et « Gérer les traceurs » est le seul à devoir exécuter du script. */
.lien-nu {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-align: left;
}

@media (max-width: 900px) {
  .consent {
    left: 12px;
    right: 12px;
    max-width: none;
    /* Au-dessus du bouton de téléchargement, qui est fixe lui aussi : sans ce
       décalage, le bandeau se posait dessus et cachait l'appel à l'action. */
    bottom: calc(var(--barre-bas, 96px) + 12px);
  }

  .consent-carte {
    padding: 20px;
  }
}
