/* ============================================================================
   VENTILO.IO - COUCHE 3/4 : COMPOSANTS
   ----------------------------------------------------------------------------
   Le design system. Un composant = un bloc .vs-nom, ses éléments en
   .vs-nom__element, ses variantes en .vs-nom--variante, ses états pilotés en
   JS en .is-etat.

   PARTAGE DES RÔLES AVEC BOOTSTRAP
   Bootstrap 5.3 fournit la mécanique : grille (row/col), boutons (.btn),
   accordéon, menu déroulant, repli mobile, infobulles. On ne la réécrit pas,
   on la réaccorde par les variables --bs-* (voir tokens.css) et par les
   quelques surcharges ci-dessous. Le DS fournit tout ce que Bootstrap n'a
   pas, en .vs-*. Aucune classe maison sans préfixe.
   ========================================================================== */


/* ══════════════════════════════════════════════════════════════════════════
   BOUTONS  (mécanique Bootstrap, apparence Ventilo)
   ══════════════════════════════════════════════════════════════════════════
   Trois niveaux, jamais plus sur un même écran :
     .btn.btn-primary          action principale, une seule par bloc
     .btn.btn-outline-primary  action secondaire
     .vs-link-arrow            action tertiaire, un lien fléché

   .vs-btn-link est HORS de cette échelle : c'est un lien qui a la hauteur et
   l'alignement d'un bouton, pour l'action qui ne dispute rien à l'action
   principale (« Connexion » à côté de « Démarrer maintenant »). Il ne compte
   donc pas comme un 4ᵉ niveau.

   Le site ne met PAS d'ombre sur les boutons. Cette règle ne s'applique
   volontairement pas au focus clavier : voir plus bas. */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--v-space-2);
  min-height: var(--v-btn-h);
  padding-block: 0;
  padding-inline: var(--v-space-5);
  font-size: var(--v-text-sm);
  font-weight: var(--v-weight-semibold);
  line-height: 1;
  border-radius: var(--v-radius-btn);
  box-shadow: none;
  /* Aucun effet au clic (user, 03/09/2026) : ni deplacement, ni changement
     d'echelle. Seules la couleur de fond et le contour bougent, au survol. */
  transition: background-color var(--v-duration-1) var(--v-ease),
              border-color     var(--v-duration-1) var(--v-ease),
              color            var(--v-duration-1) var(--v-ease);
}
.btn:hover,
.btn:active,
.btn.active,
.btn:focus:not(:focus-visible) { box-shadow: none; }

/* L'EXCEPTION à « pas d'ombre sur les boutons ».
   L'ancienne feuille écrivait box-shadow:none !important sur .btn, ce qui
   emportait aussi l'anneau de focus de Bootstrap et rendait toute la
   navigation clavier aveugle. On rétablit un anneau explicite. */
.btn:focus-visible {
  box-shadow: var(--v-focus-ring);
  outline: none;
}

/* La taille du hero. min-height plutot que height : un libelle qui passe sur
   deux lignes agrandit le bouton au lieu d'etre rogne. */
.btn-lg {
  min-height: var(--v-btn-h-lg);
  padding-inline: var(--v-space-6);
  font-size: var(--v-text-body);
  border-radius: var(--v-radius-btn-lg);
}
.btn-sm {
  min-height: var(--v-btn-h-sm);
  padding-inline: var(--v-space-4);
  border-radius: var(--v-radius-btn-sm);
}

.btn-primary {
  --bs-btn-bg: var(--v-primary);
  --bs-btn-border-color: var(--v-primary);
  --bs-btn-hover-bg: var(--v-primary-hover);
  --bs-btn-hover-border-color: var(--v-primary-hover);
  --bs-btn-active-bg: var(--v-primary-active);
  --bs-btn-active-border-color: var(--v-primary-active);
}

/* Le contour secondaire n'utilise PAS --v-border : un bord de bouton est un
   contour d'action, pas un filet de séparation. */
.btn-outline-primary {
  --bs-btn-color: var(--v-primary);
  --bs-btn-border-color: var(--v-primary);
  --bs-btn-hover-bg: var(--v-primary);
  --bs-btn-hover-border-color: var(--v-primary);
  --bs-btn-active-bg: var(--v-primary-active);
  --bs-btn-active-border-color: var(--v-primary-active);
  background: transparent;
}

/* LIEN À HAUTEUR DE BOUTON. Ni fond, ni contour : rien qui pèse. Il se pose à
   côté d'un bouton plein sans lui disputer le regard, tout en gardant la
   hauteur et l'alignement d'un .btn - deux éléments d'une même rangée ne
   doivent pas se décaler d'un pixel. Au survol, la langue du site : la couleur
   passe au violet et le libellé se souligne. */
.vs-btn-link {
  --bs-btn-color: var(--v-text);
  --bs-btn-bg: transparent;
  --bs-btn-border-color: transparent;
  --bs-btn-hover-color: var(--v-primary);
  --bs-btn-hover-bg: transparent;
  --bs-btn-hover-border-color: transparent;
  --bs-btn-active-color: var(--v-primary);
  --bs-btn-active-bg: transparent;
  --bs-btn-active-border-color: transparent;
  padding-inline: var(--v-space-3);
}
.vs-btn-link:hover,
.vs-btn-link:focus-visible {
  text-decoration: underline;
  text-underline-offset: 0.2em;
  text-decoration-thickness: 1px;
}

/* Contour cyan, indépendant du .btn-outline-info de Bootstrap dont la teinte
   n'est pas la nôtre. Texte en -ink pour rester lisible. */
.vs-btn-outline-info {
  --bs-btn-color: var(--v-info-ink);
  --bs-btn-border-color: var(--v-info-ink);
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: var(--v-info-ink);
  --bs-btn-hover-border-color: var(--v-info-ink);
  --bs-btn-active-color: #fff;
  --bs-btn-active-bg: var(--v-info-ink);
  --bs-btn-active-border-color: var(--v-info-ink);
}

/* Connexion par un fournisseur tiers. Fond clair, texte sombre : sur le hero
   sombre il se distingue du bouton principal violet sans avoir besoin d'un
   contour. Le « G » de Google est une MARQUE, pas une icône d'interface : il
   garde ses quatre couleurs officielles et échappe donc à la règle « une seule
   famille d'icônes », au même titre que les logos Stripe et HelloAsso. */
/* Bouton Google en CONTOUR sur fond sombre (user, 04/09/2026) : il n'est pas
   l'action principale du hero, il ne doit donc pas peser autant que le bouton
   plein qui le precede. Texte blanc, contour blanc a demi-opacite, fond
   transparent ; au survol c'est le fond qui se remplit de blanc et le texte qui
   passe en nuit - l'echange classique d'un bouton a contour.

   Le « G » de Google garde ses quatre couleurs de marque dans les deux etats :
   c'est un logo, il ne se recolorie pas. */
.vs-btn-google {
  --bs-btn-color: var(--v-on-dark);
  --bs-btn-bg: transparent;
  --bs-btn-border-color: rgba(255, 255, 255, .55);
  --bs-btn-hover-color: var(--v-secondary);
  --bs-btn-hover-bg: var(--v-on-dark);
  --bs-btn-hover-border-color: var(--v-on-dark);
  --bs-btn-active-color: var(--v-secondary);
  --bs-btn-active-bg: #e6e9ee;
  --bs-btn-active-border-color: #e6e9ee;
  gap: 0.65rem;
}

.vs-btn-google__mark { width: 1.15em; height: 1.15em; flex-shrink: 0; }

/* BOUTON PARTENAIRE : « Commencer avec » suivi du logo de la marque.

   Le logo est laisse INTACT : ni recolore, ni monochrome. C'est un fichier de
   marque, ses couleurs sont sa signature - l'anneau tricolore de HelloAsso ne
   se remplace pas par du blanc. C'est ce qui impose le FOND CLAIR du bouton
   (user, 04/09/2026) : le mot est ecrit en bleu nuit, sur la photo sombre du
   hero il faut lui donner sa surface.

   Il est dimensionne en EM et par sa hauteur seule : il suit le corps du
   bouton, et sa largeur reste celle du fichier quel que soit le mot ecrit. */
.vs-btn-partner {
  --bs-btn-color: var(--v-secondary);
  --bs-btn-bg: var(--v-on-dark);
  --bs-btn-border-color: var(--v-on-dark);
  --bs-btn-hover-color: var(--v-secondary);
  --bs-btn-hover-bg: #e6e9ee;
  --bs-btn-hover-border-color: #e6e9ee;
  --bs-btn-active-color: var(--v-secondary);
  --bs-btn-active-bg: #dde1e8;
  --bs-btn-active-border-color: #dde1e8;
  gap: 0.5rem;
}
.vs-btn-partner__mark {
  display: block;
  width: auto;
  height: 1.55em;
  flex-shrink: 0;
}

/* Action tertiaire : un lien fléché. La flèche avance au survol, c'est le
   seul mouvement autorisé sur un lien. */
.vs-link-arrow {
  display: inline-flex;
  align-items: center;
  gap: var(--v-space-1);
  color: var(--v-primary);
  font-weight: var(--v-weight-bold);
  font-size: 0.95rem;
  text-decoration: none;
}
.vs-link-arrow .vs-ico { transition: transform var(--v-duration-2) var(--v-ease); }
.vs-link-arrow:hover { color: var(--v-primary-hover); }
.vs-link-arrow:hover .vs-ico { transform: translateX(3px); }

.vs-link-arrow--light { color: var(--v-on-dark); }
.vs-link-arrow--light:hover { color: var(--v-on-dark); text-decoration: underline; }

/* Retour en arrière, en tête de page intérieure. */
.vs-back {
  display: inline-flex;
  align-items: center;
  gap: var(--v-space-1);
  color: var(--v-muted);
  font-weight: var(--v-weight-semibold);
  text-decoration: none;
}
.vs-back:hover { color: var(--v-primary); }


/* ══════════════════════════════════════════════════════════════════════════
   CARTE
   ══════════════════════════════════════════════════════════════════════════
   Rayon 24, AUCUNE bordure, AUCUNE ombre. C'est le fond qui détoure la carte,
   pas un trait, d'où la règle unique :

       UNE CARTE PREND TOUJOURS LE FOND OPPOSÉ À SA SECTION.
       .vs-card          blanche   -> sur .vs-section--tinted
       .vs-card--tinted  teintée   -> sur .vs-section (blanche)

   Blanc sur blanc ou teinté sur teinté, la carte disparaît : c'est une erreur,
   pas un choix esthétique.

   Elle ne réagit au survol QUE si elle est entièrement cliquable, c'est-à-dire
   si elle porte .vs-card--link. Une carte dont seul le lien du bas est
   cliquable ne bouge pas : faire bouger une zone morte promet un clic qui
   n'existe pas. */
.vs-card {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: var(--v-space-8) 1.75rem;
  border: 0;
  border-radius: var(--v-radius-lg);
  background: var(--v-surface);
  box-shadow: none;
}

/* La carte posée sur une section blanche. */
.vs-card--tinted { background: var(--v-bg); }

.vs-card--link {
  text-decoration: none;
  color: inherit;
}
/* AUCUN deplacement au survol (user, 04/09/2026) : la carte ne se souleve plus.
   C'est la regle du site - le survol change la teinte ou souligne, il ne bouge
   pas les blocs. Une carte qui monte de 4 px decale aussi ses voisines a l'oeil
   et fait vibrer une grille entiere. */
/* Le TITRE se souligne au survol de la carte, comme le nom d'une application
   ou d'un format ailleurs sur le site : c'est le vocabulaire du lien, applique
   a la seule partie qui nomme la destination. */
.vs-card--link:hover .vs-card__head span,
.vs-card--link:focus-visible .vs-card__head span {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
}

/* Icône à gauche, texte à droite, le tout centré sur la hauteur. */
.vs-card--row {
  flex-direction: row;
  align-items: center;
  gap: 1.4rem;
}

/* Titre de carte precede de son illustration, sur UNE ligne. L'icone est
   alignee sur le milieu du titre et ne se comprime jamais - un titre qui passe
   sur deux lignes ne doit pas la deformer. */
.vs-card__head {
  display: flex;
  align-items: center;
  gap: var(--v-space-3);
  margin: 0 0 var(--v-space-2);
}
.vs-card__icon {
  flex: 0 0 auto;
  display: block;
  width: 2.75rem;
  height: 2.75rem;
  object-fit: contain;
}

.vs-card__body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-width: 0;
}
.vs-card__body > :last-child { margin-bottom: 0; }

.vs-card__title { font-size: var(--v-text-h3); color: var(--v-secondary); }
.vs-card__text  { color: var(--v-muted); margin-bottom: 0; }

/* Le lien de bas de carte est épinglé au bas : sur une rangée de cartes de
   contenus inégaux, les liens s'alignent au lieu de flotter à des hauteurs
   différentes. */
.vs-card__more {
  margin-top: auto;
  padding-top: var(--v-space-5);
  align-self: flex-start;
}

/* Badges de magasin d'applications. Leur hauteur est celle d'un bouton du
   site : posee a cote d'une carte qui porte un bouton, la rangee s'aligne.
   Les fichiers sont les badges officiels, on ne les redessine pas et on ne
   leur ajoute ni cadre ni ombre - Apple comme Google l'interdisent. */
.vs-stores {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--v-space-3);
}
/* Les badges sont des LIENS vers les magasins (et non plus des boutons inertes) :
   display block pour que la zone cliquable epouse l'image au pixel, et pas de
   soulignage - l'image EST le badge, un trait sous elle se lirait comme un
   defaut d'affichage. */
.vs-store {
  display: block;
  padding: 0;
  border: 0;
  background: none;
  line-height: 0;
  cursor: pointer;
  text-decoration: none;
  /* Rayon d'une commande de sa hauteur : le badge fait 40px, comme un bouton
     normal (user, 04/09/2026). */
  border-radius: var(--v-radius-btn);
}
.vs-store img {
  display: block;
  height: var(--v-btn-h);
  width: auto;
}

/* BADGE « WEB APP ». Le pendant, cote plateforme web, des badges App Store /
   Google Play de la carte mobile : meme hauteur (40px), meme rectangle sombre
   arrondi, meme lecture « petite ligne + grosse ligne ». Il n'y a pas de fichier
   image (pas de logo PWA officiel embarque, hors charte Solar-inline) : le badge
   est construit en HTML, l'icone est le globe Solar. Le tooltip Bootstrap
   explique ce qu'est une web app installable. */
.vs-webapp-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--v-space-3);
  height: var(--v-btn-h);
  padding: 0 var(--v-space-4) 0 var(--v-space-3);
  border-radius: 6px;
  background: var(--v-secondary);
  color: var(--v-on-dark);
  text-decoration: none;
  line-height: 1;
  transition: background-color var(--v-duration-1) var(--v-ease);
}
/* Hover uniquement sur les appareils à survol réel : sur tactile, le :hover se
   déclenche au tap et RESTE collé (le badge virait au violet et y restait
   pendant le scroll). */
@media (hover: hover) {
  /* Au survol : fond violet et TOUT le contenu en blanc (grosse ligne, icône
     via currentColor, et la petite ligne qui est muette au repos). */
  .vs-webapp-badge:hover { background: var(--v-primary); color: #fff; }
  .vs-webapp-badge:hover .vs-webapp-badge__small { color: #fff; }
}
.vs-webapp-badge__mark { display: inline-flex; font-size: 1.5rem; }
.vs-webapp-badge__text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  text-align: left;
}
.vs-webapp-badge__small {
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: var(--v-tracking-label);
  color: var(--v-on-dark-muted);
}
.vs-webapp-badge__big {
  font-size: 1.05rem;
  font-weight: var(--v-weight-bold);
}
/* Mobile : le badge devient un vrai CTA pleine largeur, plus grand, contenu
   centre (comme la carte s'empile et prend toute la largeur). */
@media (max-width: 991.98px) {
  .vs-webapp-badge {
    width: 100%;
    height: var(--v-btn-h-lg);
    justify-content: center;
    gap: var(--v-space-4);
    border-radius: 8px;
  }
  .vs-webapp-badge__mark { font-size: 2rem; }
  .vs-webapp-badge__small { font-size: 0.72rem; }
  .vs-webapp-badge__big { font-size: 1.3rem; }
}


/* Contenu à venir. Le seul endroit du site où un trait pointillé est admis :
   il signale explicitement un vide assumé. */
.vs-placeholder {
  display: inline-flex;
  align-items: center;
  gap: var(--v-space-2);
  padding: var(--v-space-4) var(--v-space-5);
  border: 1px dashed var(--v-border);
  border-radius: var(--v-radius-md);
  background: var(--v-bg);
  color: var(--v-muted);
  font-weight: var(--v-weight-semibold);
}


/* ══════════════════════════════════════════════════════════════════════════
   GRILLE BENTO
   ══════════════════════════════════════════════════════════════════════════
   Des tuiles de tailles differentes qui forment quand meme un rectangle plein.
   Quatre colonnes, deux rangees : une grande tuile occupe le quart haut-gauche
   (2 x 2), deux petites la ligne du haut, une large la ligne du bas. Aucun
   trou, aucun debord.

   Les tailles sont posees sur la GRILLE et non sur les tuiles (pas de hauteur
   ecrite en dur) : deux tuiles cote a cote ont donc toujours exactement la meme
   hauteur, quel que soit leur texte. C'est la seule facon de garder l'ensemble
   homogene quand les contenus sont inegaux.

   grid-auto-rows en minmax : la rangee ne descend jamais sous sa valeur, mais
   elle s'ouvre si un texte deborde. Une hauteur fixe couperait le texte, une
   hauteur libre laisserait les rangees se tasser.

   La valeur decide de la hauteur de TOUT le bloc : deux rangees plus leur
   ecart, auxquels le chapeau et les marges de section ajoutent 17rem.

   A 23rem l'ensemble fait 1024 px : la contrainte du « tout dans un ecran » est
   LEVEE (user, 03/09/2026), le bas du bloc demande un debut de defilement. Le
   choix est assume - ce sont les captures produit qu'on vient voir, et c'est
   leur taille qui compte ici, pas le fait que le bloc rentre d'un coup. */
.vs-bento {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: auto;
  gap: var(--v-space-4);
}
.vs-widget--lg   { grid-column: span 2; grid-row: span 2; }
.vs-widget--wide { grid-column: span 2; }

/* DUO : deux tuiles a parts egales sur une seule rangee, plus haute. Meme
   langue de tuile que la grille a quatre tailles, autre rythme - c'est la
   disposition qui change d'une section a l'autre, jamais le vocabulaire. */
.vs-bento--duo {
  grid-template-columns: repeat(2, 1fr);
  grid-auto-rows: auto;
}

/* TOUR : une colonne haute a gauche, deux tuiles a cote d'elle, une large en
   dessous. Trois colonnes, deux rangees, aucun trou.

   La colonne haute est faite pour un visuel PORTRAIT - une capture de
   telephone, par exemple. C'est la disposition a choisir quand la piece
   maitresse de la section est verticale : la garder dans une tuile large
   obligerait a la reduire entre deux marges.

   Les spans sont poses par rang et non par modificateur de tuile : la place
   d'une tuile depend de sa POSITION dans la section, pas d'une classe qu'il
   faudrait penser a deplacer en meme temps que le contenu. */
.vs-bento--tour {
  grid-template-columns: repeat(3, 1fr);
  /* Hauteur LIBRE : depuis que le cadre suit son image, un plancher de rangee
     ne servirait plus le cadrage - il ajouterait seulement du vide sous les
     textes. La colonne haute tient sa hauteur de sa capture de telephone. */
  grid-auto-rows: auto;
}
.vs-bento--tour > :nth-child(1) { grid-row: span 2; }
.vs-bento--tour > :nth-child(4) { grid-column: span 2; }

/* TRIO : une large et une etroite en haut, une pleine largeur en dessous.
   Trois tuiles, deux rangees, aucun trou - c'est la tuile du bas qui reprend
   toute la largeur laissee libre.

   Les spans sont poses par rang et non par modificateur de tuile : la place
   d'une tuile depend de sa POSITION dans la section, pas d'une classe qu'il
   faudrait penser a deplacer en meme temps que le contenu. */
.vs-bento--trio {
  grid-template-columns: repeat(4, 1fr);
  /* Hauteur LIBRE : c'est desormais l'image qui decide de la hauteur de son
     cadre, la rangee n'a plus a viser le format des captures. */
  grid-auto-rows: auto;
}
/* SOLO : une seule tuile, sur TOUTE la largeur du conteneur (user, 04/09/2026)
   - comme la tuile du bas de la section evenementiel. Ni borne, ni centrage :
   le bloc s'aligne sur le meme bord gauche que les chapeaux et que toutes les
   autres grilles de la page.

   La rangee est plus haute que celle des autres dispositions parce que la
   tuile est plus large : c'est ce qui donne a sa capture le meme format
   (1296 x 439, soit 2,95) qu'aux visuels deja produits. Mesure a l'appui, pas
   au jugé - une rangee trop courte et l'image se reduit entre deux marges. */
.vs-bento--solo {
  grid-template-columns: 1fr;
  grid-auto-rows: auto;
}

.vs-bento--trio > :nth-child(1) { grid-column: span 3; }
.vs-bento--trio > :nth-child(2) { grid-column: span 1; }
.vs-bento--trio > :nth-child(3) { grid-column: span 4; }

/* La tuile. Tout ce qui fait sa langue est ici et vaut pour les quatre :
   meme fond, meme rayon, meme retrait, icone en haut a gauche, texte cale en
   bas. Ce qui varie d'une tuile a l'autre, c'est sa PLACE dans la grille, et
   rien d'autre. */
.vs-widget {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--v-space-2);
  padding: var(--v-space-6);
  border-radius: var(--v-radius-lg);
  /* Regle de surface, comme les cartes : BLANCHE par defaut (donc posee sur une
     section teintee), et --tinted quand la section est blanche. Ni bordure ni
     ombre, le site n'en a pas. */
  background: var(--v-surface);
  /* La capture file jusqu'aux bords de la tuile : c'est le rayon de la TUILE
     qui la detoure, elle n'a donc pas a porter le sien. */
  overflow: hidden;
}
/* Icone et titre sur une seule ligne. Empiles, ils prenaient une soixantaine
   de pixels par tuile ; la hauteur gagnee revient a la capture, qui est ce
   qu'on vient voir. */
.vs-widget__head {
  display: flex;
  align-items: center;
  gap: var(--v-space-2);
  /* margin-top auto : le titre et le texte qui le suit sont POUSSES EN BAS de
     la tuile. C'est le cadre qui jouait ce role tant qu'il s'etirait (flex 1) ;
     depuis qu'il prend la hauteur de son image, la place qui reste doit passer
     ICI, entre l'image et le texte. Sans cette ligne, le texte remonte coller
     l'image et le vide s'accumule sous lui : dans une rangee de tuiles aux
     images de hauteurs differentes, les textes ne finissent plus sur la meme
     ligne. */
  margin: auto 0 0;
}
.vs-widget__icon { display: block; flex: 0 0 auto; width: 2rem; height: 2rem; object-fit: contain; }
.vs-widget--lg .vs-widget__icon { width: 2.75rem; height: 2.75rem; }

.vs-widget__title {
  font-weight: var(--v-weight-bold);
  font-size: var(--v-text-h3);
  line-height: var(--v-lh-snug);
  color: var(--v-secondary);
}
.vs-widget__text {
  margin: 0;
  color: var(--v-muted);
  font-size: var(--v-text-sm);
  max-width: var(--v-measure);
}

/* L'EMPLACEMENT PRODUIT, en HAUT de la tuile. Il prend toute la hauteur qui
   reste au-dessus du texte (flex:1) et deborde du retrait pour toucher les
   trois bords : une capture ne doit pas flotter au milieu d'une marge, elle
   doit avoir l'air posee DANS la tuile.

   Pas d'aspect-ratio ici, contrairement aux autres cadres media du site : la
   hauteur est imposee par la rangee de la grille, un ratio en plus se battrait
   avec elle. C'est le object-fit:cover du cadre qui rattrape le cadrage, et la
   taille de fichier conseillee est affichee dans l'emplacement vide. */
/* Selecteur DOUBLE (.vs-widget__media.vs-media) et non simple : l'emplacement
   porte aussi la classe .vs-media, definie plus bas dans ce fichier, qui impose
   un ratio 16/9 et un fond. A specificite egale, c'est la derniere regle qui
   gagne - donc la sienne. Ce cran de specificite est la seule facon de la
   reprendre sans dependre de l'ordre des blocs. */
.vs-widget__media.vs-media {
  /* base ZERO : le cadre prend la place que la rangee lui laisse une fois le
     texte pose, et rien de plus. Surtout pas de base en pourcentage - dans une
     rangee de hauteur automatique, un pourcentage retombe sur la taille du
     CONTENU, donc sur le format du fichier, et la tuile se met a grandir avec
     l'image. */
  /* LE CADRE SUIT L'IMAGE, ET NON L'INVERSE (user, 04/09/2026). Il prend la
     hauteur que l'image demande a la largeur de la tuile - d'ou flex 0 0 auto
     et non 1 1 0. Etire a la hauteur de la rangee, il devenait plus haut que
     large des que la tuile se resserrait, et le object-fit:contain - qui existe
     pour ne jamais recadrer une maquette d'interface - laissait alors deux
     bandes de fond, jusqu'a 164 px vers 1100 px de large. La place qui reste
     dans la tuile passe donc SOUS le texte, ou elle se lit comme une marge,
     plutot qu'autour de la capture, ou elle se lit comme un trou. */
  flex: 0 0 auto;
  align-self: stretch;
  order: -1;            /* premier de la tuile, quel que soit l'ordre du HTML */
  width: auto;
  /* La capture touche le haut ET les cotes de la tuile, et rien d'autre.
     Essai du 03/09/2026, annule : la retirer du bord haut tout en la laissant
     filer sur les cotes donnait une image aux angles carres posee sous une
     bande de fond, alors que la tuile, elle, est arrondie - ca se lisait comme
     un decalage. Un retrait ne vaut que s'il est le meme sur les quatre cotes,
     et il faudrait alors reprendre le format de toutes les captures. */
  margin: calc(-1 * var(--v-space-6)) calc(-1 * var(--v-space-6)) var(--v-space-4);
  border-radius: 0;
  /* PAS de ratio : la hauteur vient de la rangee de la grille. Un 16/9 ici
     rendrait chaque tuile aussi haute que sa largeur le decide, et le bloc ne
     tiendrait plus dans un ecran. */
  aspect-ratio: auto;
  /* Surface opposee a celle de la tuile : l'emplacement reserve se voit, et une
     capture posee dessus garde un fond neutre. */
  background: var(--v-bg);
}

/* UNE VRAIE CAPTURE, pas un emplacement vide. Deux exigences qui se
   contredisent, tranchees dans cet ordre (user, 03/09/2026) :

   1. la TUILE NE GROSSIT PAS. Le cadre garde donc flex-basis 0 : il prend la
      place que la rangee lui laisse, exactement comme un emplacement vide, et
      la grille ne bouge pas d'un pixel quand une capture arrive ;
   2. la capture n'est PAS RECADREE. D'ou contain et non cover : on la voit
      entiere, telle qu'elle a ete composee.

   Le prix, c'est la place perdue de part et d'autre quand le fichier n'a pas le
   format de son cadre - un visuel portrait dans un cadre paysage laisse deux
   marges. Fournir la capture AU FORMAT ANNONCE dans l'emplacement vide, et il
   n'y a plus ni recadrage ni marge.

   Fond transparent : c'est celui de la tuile qui se voit autour du visuel,
   plutot qu'un deuxieme aplat qui dessinerait un cadre dans le cadre. */
.vs-widget__media.vs-media:not(.vs-media--empty) { background: transparent; }
/* height auto : sans elle, .vs-media img etire l'image a 100 % du cadre, et le
   cadre ne peut plus tenir sa hauteur de l'image. */
.vs-widget__media.vs-media img { object-fit: contain; height: auto; }
/* L'emplacement VIDE n'a pas d'image pour lui donner sa hauteur : il garde un
   minimum, sinon il se reduit a la hauteur de son libelle. */
.vs-widget__media.vs-media--empty { min-height: 11rem; }
/* Exception pour les PHOTOS : elles remplissent le cadre et se recadrent.
   Rien a preserver dans leur composition, alors qu'une maquette d'interface
   perdrait son cadrage. Le sujet est au centre, c'est donc de la que part la
   coupe. */
.vs-widget__media--cover img { object-fit: cover; object-position: center; }
.vs-widget--tinted { background: var(--v-bg); }
.vs-widget--tinted .vs-widget__media.vs-media { background: var(--v-surface); }

@media (max-width: 991.98px) {
  /* Deux colonnes : la grande tuile perd sa hauteur double, sinon elle laisse
     un trou a cote d'elle. Toutes les tuiles deviennent des demi-largeurs. */
  .vs-bento { grid-template-columns: repeat(2, 1fr); }
  .vs-widget--lg { grid-row: span 1; }
}
@media (max-width: 991.98px) {
  /* La tour se replie en deux colonnes : la colonne haute garde sa hauteur
     double, elle reste la piece maitresse. */
  .vs-bento--tour { grid-template-columns: repeat(2, 1fr); }
  .vs-bento--tour > :nth-child(4) { grid-column: span 1; }
  /* Le trio ne survit pas au repli : une tuile d'un quart de largeur devient
     une colonne trop etroite pour une capture. Tout passe pleine largeur. */
  .vs-bento--trio { grid-template-columns: 1fr; }
  /* :nth-child(n) et non * : les spans du haut sont poses par :nth-child, qui
     pese plus lourd qu'un selecteur universel. Avec *, les tuiles gardaient
     leur span de 3 et 4 colonnes dans une grille qui n'en a plus qu'une, et la
     grille creait des colonnes implicites pour les loger. */
  .vs-bento--trio > :nth-child(n) { grid-column: span 1; }
}
@media (max-width: 767.98px) {
  .vs-bento--duo { grid-template-columns: 1fr; }
}

@media (max-width: 575.98px) {
  .vs-bento { grid-template-columns: 1fr; }
  .vs-bento--tour > :nth-child(n) { grid-row: span 1; grid-column: span 1; }
  .vs-widget--lg,
  .vs-widget--wide { grid-column: span 1; }
}


/* ══════════════════════════════════════════════════════════════════════════
   CADRE MÉDIA
   ══════════════════════════════════════════════════════════════════════════
   Un bloc au format imposé qui accueille une image. Le ratio est porté par le
   CADRE et non par le fichier : la place est donc réservée avant même que
   l'image arrive, il n'y a aucun décalage de mise en page au chargement, et
   remplacer le visuel par un autre de dimensions différentes ne bouge rien.

   Il suit la règle de surface : teinté sur une section blanche, blanc sur une
   section teintée. */
.vs-media {
  overflow: hidden;
  border-radius: var(--v-radius-lg);
  background: var(--v-bg);
  aspect-ratio: 16 / 9;
}
.vs-media--white { background: var(--v-surface); }

/* Aucun fond. Pour un visuel qui porte deja le sien - un detoure, une photo de
   produit sur blanc - le cadre ne sert plus qu'a reserver le ratio. */
.vs-media--bare { background: none; }

/* Format large, pour un visuel panoramique. Le ratio est ecrit avec les
   dimensions reelles du fichier plutot que simplifie (615/266) : on
   retrouve d'un coup d'oeil la taille a fournir. */
.vs-media--wide { aspect-ratio: 2460 / 1064; }

/* Ratio du visuel « appareils » du CDN : 5000 x 3333, soit 3/2 a 0,01 % pres.
   Le cadre suit le fichier, et non l'inverse : a 2460/1064 le cadrage coupait
   le haut de l'ecran et le bas du portable. */
.vs-media--3x2 { aspect-ratio: 3 / 2; }

/* Format d'une maquette de telephone : 1200 x 1600, soit trois quarts. */
.vs-media--3x4 { aspect-ratio: 3 / 4; }

.vs-media img,
.vs-media video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Bloc qui remonte sur le média qui le précède : le bas de l'image est
   recouvert, ce qui soude les deux au lieu de les empiler. Le z-index vient de
   l'échelle, comme partout ailleurs.

   EXCEPTION DOCUMENTÉE À LA RÈGLE DE SURFACE : une carte posée sur une image
   est BLANCHE quelle que soit la section. C'est l'image qui joue le rôle du
   fond contrastant, et l'intention de la règle - la carte se détache de ce
   qu'il y a derrière - est respectée.

   ATTENTION : posé sur une .row, la gouttière haute des colonnes (24 px avec
   g-4) se soustrait au recouvrement. La valeur déclarée ici n'est donc pas le
   recouvrement visible ; mesurer plutôt que déduire. */
.vs-overlap {
  position: relative;
  z-index: var(--v-z-raised);
  margin-top: calc(-1 * var(--v-space-16));
}
.vs-overlap--sm { margin-top: calc(-1 * var(--v-space-10)); }
.vs-overlap--lg { margin-top: calc(-1 * var(--v-space-24)); }

/* Sous 768 px les colonnes s'empilent : un recouvrement masquerait alors
   l'image entière, on repasse en simple succession. */
@media (max-width: 767.98px) {
  .vs-overlap,
  .vs-overlap--sm,
  .vs-overlap--lg { margin-top: var(--v-space-6); }
}

/* Emplacement encore vide. Volontairement visible : un cadre gris muet se
   déploie sans qu'on le remarque, celui-ci se signale. */
.vs-media--empty {
  display: grid;
  place-items: center;
  gap: var(--v-space-2);
  color: var(--v-muted);
  font-weight: var(--v-weight-semibold);
  text-align: center;
}
.vs-media--empty .vs-ico { font-size: 2rem; }


/* ══════════════════════════════════════════════════════════════════════════
   PASTILLE D'ICÔNE
   ══════════════════════════════════════════════════════════════════════════
   Un carré arrondi, aplat teinté, icône Bootstrap Icons au centre. Une seule
   famille d'icônes sur tout le site.

   La couleur de la pastille est décorative : elle range visuellement, elle ne
   porte aucune information. Un contenu qui n'existe que par la couleur de sa
   pastille est un contenu perdu pour qui ne la distingue pas. */
.vs-icon {
  display: inline-grid;
  place-items: center;
  flex-shrink: 0;
  width: 3.25rem;
  height: 3.25rem;
  border-radius: var(--v-radius-md);
  background: var(--v-primary-bg);
  color: var(--v-primary);
  font-size: 1.5rem;
}
.vs-icon--lg { width: 4.5rem; height: 4.5rem; font-size: 2.1rem; }

.vs-icon--info    { background: var(--v-info-bg);    color: var(--v-info-ink); }
.vs-icon--success { background: var(--v-success-bg); color: var(--v-success-ink); }
.vs-icon--warning { background: var(--v-warning-bg); color: var(--v-warning-ink); }
.vs-icon--error   { background: var(--v-error-bg);   color: var(--v-error-ink); }

/* Couleur de marque d'un tiers. Le vert clair officiel de WhatsApp tombe à
   1,77:1 sur son propre aplat : on prend son vert foncé de marque, 3,7:1, qui
   passe le seuil des éléments graphiques. */
.vs-icon--whatsapp { background: #e0f8e9; color: #128c7e; }


/* ══════════════════════════════════════════════════════════════════════════
   MUR D'APPLICATIONS
   ══════════════════════════════════════════════════════════════════════════
   Une tuile carrée, l'icône au centre, le nom dessous. Rien d'autre : pas de
   description, pas de lien « en savoir plus ». La grille se lit d'un coup
   d'œil et remplace les rangées de trois cartes, qui sont le patron de mise en
   page le plus attendu du web.

   La tuile suit la règle de surface du site : blanche sur une section teintée,
   donc --tinted sur une section blanche. Toute la cellule est un lien, elle a
   donc le droit de réagir au survol.

   Repère de calibrage des petits écrans : odoo.com/fr_FR, tuile 80x80, nom
   en 14 px dessous. Sur bureau la tuile suit la largeur de sa colonne. */
.vs-appwall {
  display: grid;
  /* Valeurs de repli, pour les petits ecrans : 3 colonnes, grille bornee et
     centree. Six tuiles y seraient illisibles. La disposition demandee - six
     par rangee sur toute la largeur - est posee plus bas, dans la requete
     >= 992 px. */
  grid-template-columns: repeat(3, 1fr);
  gap: var(--v-space-6) var(--v-space-4);
  padding: 0;
  max-width: 44rem;
  margin-inline: auto;
  list-style: none;
}

.vs-app {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--v-space-3);
  color: var(--v-secondary);
  text-decoration: none;
}

.vs-app__tile {
  display: grid;
  place-items: center;
  width: 80px;
  height: 80px;
  border-radius: var(--v-radius-md);
  background: var(--v-surface);
  color: var(--v-primary);
  font-size: 2rem;
}
.vs-app--tinted .vs-app__tile { background: var(--v-bg); }

/* Douzieme tuile : le compte de ce qui n'est pas montre, a la place d'une
   illustration. Le chiffre remplit la tuile comme le ferait une icone, il est
   donc dimensionne en em de la tuile (font-size heritee) et non en pixels. */
.vs-app__more {
  font-weight: var(--v-weight-bold);
  letter-spacing: -0.02em;
  line-height: 1;
  color: var(--v-primary);
}

/* Illustration posee dans la tuile, a la place du glyphe. Registre Iconly 3D,
   fichiers du CDN, fond transparent. */
.vs-app__icon { display: block; width: 52px; height: 52px; object-fit: contain; }
@media (max-width: 575.98px) { .vs-app__icon { width: 40px; height: 40px; } }

/* La couleur ne fait que ranger visuellement : le nom sous la tuile porte
   l'information, jamais la teinte seule. */
.vs-app__tile--info    { color: var(--v-info-ink); }
.vs-app__tile--success { color: var(--v-success-ink); }
.vs-app__tile--warning { color: var(--v-warning-ink); }
.vs-app__tile--error   { color: var(--v-error-ink); }

.vs-app__name {
  font-size: var(--v-text-sm);
  font-weight: var(--v-weight-semibold);
  line-height: var(--v-lh-snug);
  text-align: center;
  text-wrap: balance;
}

/* Survol : le NOM se souligne, rien d'autre (user, 03/09/2026). La tuile reste
   strictement immobile et de la meme couleur - ni deplacement, ni ombre, ni
   changement de fond. C'est le vocabulaire du lien, applique a la seule partie
   qui porte le texte : on comprend que la cellule mene quelque part sans que la
   grille bouge. Le trait est repris au focus clavier, en plus de l'anneau, pour
   que les deux entrees donnent le meme signal. */
.vs-app:hover .vs-app__name,
.vs-app:focus-visible .vs-app__name {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
}

/* En 3 colonnes la meme borne laisserait 220 px par colonne : on la resserre
   pour garder l'ecart entre apps constant d'une disposition a l'autre. */
@media (max-width: 991.98px) { .vs-appwall { max-width: 26rem; } }

/* BUREAU : six par rangee, sur TOUTE la largeur du conteneur (user,
   03/09/2026). Le conteneur est le meme que celui de l'en-tete : la premiere
   tuile part donc a l'aplomb du logo et la sixieme finit a l'aplomb des
   boutons. Plus de borne ni de centrage, sinon le mur flotterait au milieu.

   La tuile ne garde pas ses 80 px : elle remplit sa colonne (~176 px sur
   1140), ce qui la fait grossir ET la cale exactement sur les deux bords.
   Une tuile de taille fixe, centree dans sa colonne, laisserait une trentaine
   de pixels de vide a gauche du premier module et l'alignement demande
   tomberait a cote. Avec 9 modules la deuxieme rangee en compte 3 : elle
   reste calee a gauche, sous les colonnes 1 a 3. */
@media (min-width: 992px) {
  .vs-appwall {
    grid-template-columns: repeat(6, 1fr);
    max-width: none;
    margin-inline: 0;
  }
  .vs-app__tile {
    width: 100%;
    height: auto;
    aspect-ratio: 1;
    border-radius: var(--v-radius-lg);
    /* Le glyphe Bootstrap n'est que le repli des modules sans illustration,
       mais il doit occuper la meme part de tuile qu'elle, sinon les deux
       rendus ne se ressemblent pas. */
    font-size: 4.2rem;
  }
  /* En pourcentage et non en pixels : l'illustration garde la meme place dans
     la tuile quelle que soit la largeur du conteneur. Les fichiers du CDN font
     256 px au minimum (certains 2500), il n'y a donc pas d'agrandissement
     visible meme sur ecran a densite double. */
  .vs-app__icon { width: 55%; height: auto; aspect-ratio: 1; }
  .vs-app__name { font-size: var(--v-text-body); }
}

/* Mobile : on reste a 3 colonnes avec des tuiles plus petites, plutot que des
   vignettes larges qui obligeraient a faire defiler toute la liste. */
@media (max-width: 575.98px) {
  .vs-appwall { gap: var(--v-space-5) var(--v-space-3); }
  .vs-app__tile { width: 64px; height: 64px; font-size: 1.6rem; }
}


/* ══════════════════════════════════════════════════════════════════════════
   TUILE PHOTO
   ══════════════════════════════════════════════════════════════════════════
   Une photo et un libelle dessous. Sert a montrer beaucoup de cas d'usage d'un
   coup, la ou une liste de mots ne dirait rien.

   Le nombre de colonnes divise toujours le nombre de tuiles, sinon la derniere
   rangee porte des orphelines. Dix metiers : 10 se divise par 5 et par 2, on
   n'a donc que ces deux dispositions. Passer a 3 ou 4 colonnes laisserait une
   ou deux tuiles seules en bas.

   La photo est illustrative, son alt est vide : c'est le libelle qui porte
   l'information, et le repeter en alt le ferait lire deux fois. */
.vs-tiles {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: var(--v-space-6) var(--v-space-4);
  padding: 0;
  margin: 0;
  list-style: none;
}
.vs-tile {
  display: flex;
  flex-direction: column;
  gap: var(--v-space-3);
  margin: 0;
}
.vs-tile__img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-radius: var(--v-radius-md);
}
.vs-tile__name {
  margin: 0;
  font-size: var(--v-text-sm);
  font-weight: var(--v-weight-semibold);
  line-height: var(--v-lh-snug);
  color: var(--v-secondary);
  text-align: center;
  text-wrap: balance;
}

/* Meme survol que le mur d'applications : seul le libelle se souligne, la
   photo ne bouge pas. Il n'est pose que sur les VRAIES vignettes - la rangee
   fictive n'a pas de libelle et ne recoit rien. */
.vs-tile:hover .vs-tile__name {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
}

/* Rangee fictive : une rangee de plus, coupee par la limite de section, qui
   se fond dedans avant d'y arriver. Elle ne porte aucune information (ni
   metier, ni nom), elle sert uniquement a montrer que la grille continue.

   Les vignettes gardent le format 3/2 des VRAIES : une bande letterbox ne se
   lit pas comme des cartes qui passent dessous, elle se lit comme des photos
   rognees. La rangee est tiree SOUS la limite - son bas depasse dans le bloc
   suivant - et le voile atteint sa pleine opacite pile a cette limite.

   Un VOILE, pas un masque. Le masque rendait les photos transparentes et
   laissait apparaitre le fond de cette section-ci ; le voile les recouvre de la
   couleur du bloc SUIVANT. Les cartes se fondent donc dans ce vers quoi elles
   glissent, et la partie qui depasse est peinte de la meme couleur que la
   section qu'elle recouvre : la jonction est invisible.

   D'ou --v-tiles-fade : la couleur du voile n'est pas une constante mais LA
   COULEUR DE LA SECTION SUIVANTE. Teintee dans le cas courant ; une page qui
   fait suivre la grille d'une section blanche pose le modificateur ci-dessous.
   Se tromper de couleur ici ne casse rien, ca laisse juste une bande visible
   au raccord.

   color-mix plutot qu'un rgba en dur : une seule source de couleur. Sous
   @supports parce que sans lui le degrade serait invalide, donc absent, et le
   depassement se verrait en clair sur le bloc suivant. */
.vs-tiles--ghost {
  --v-tiles-fade: var(--v-bg);
  margin-top: var(--v-space-6);
  margin-bottom: calc(var(--v-section-y) * -1);
  pointer-events: none;
}
/* La grille est suivie d'une section BLANCHE : le voile doit l'etre aussi. */
.vs-tiles--ghost--to-surface { --v-tiles-fade: var(--v-surface); }

@supports (background: color-mix(in srgb, red 50%, transparent)) {
  .vs-tiles--ghost {
    position: relative;
    margin-bottom: calc((var(--v-section-y) + 3.5rem) * -1);
  }
  .vs-tiles--ghost::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(to bottom,
        color-mix(in srgb, var(--v-tiles-fade) 0%,  transparent) 0%,
        color-mix(in srgb, var(--v-tiles-fade) 0%,  transparent) 20%,
        color-mix(in srgb, var(--v-tiles-fade) 45%, transparent) 42%,
        var(--v-tiles-fade) 61%,
        var(--v-tiles-fade) 100%);
  }
}

@media (max-width: 767.98px) {
  .vs-tiles { grid-template-columns: repeat(2, 1fr); gap: var(--v-space-5) var(--v-space-3); }
  /* 2 colonnes : les trois dernieres feraient d'autres rangees fictives. */
  .vs-tiles--ghost > :nth-child(n+3) { display: none; }
  /* DEUX RANGEES EN MOINS sur telephone (user, 04/09/2026) : en 2 colonnes les
     dix formats font cinq rangees, soit un mur a faire defiler avant d'arriver
     au discours. On en montre six, trois rangees, et la rangee fictive qui suit
     dit deja que la liste continue. Rien n'est retire du HTML : les dix formats
     restent la pour le referencement et pour le bureau. */
  .vs-tiles:not(.vs-tiles--ghost) > :nth-child(n+7) { display: none; }
}


/* ══════════════════════════════════════════════════════════════════════════
   ÉTIQUETTE
   ══════════════════════════════════════════════════════════════════════════
   Un mot dans un aplat teinté. Sert partout : jetons du simulateur, cartes du
   bandeau défilant, mentions. Deux tailles, une seule mécanique.
   Les teintes de texte sont les variantes -ink : les teintes vives de marque
   ne passent pas le seuil de lisibilité en petit corps. */
.vs-tag {
  display: inline-flex;
  align-items: center;
  gap: var(--v-space-1);
  padding: 0.22rem 0.6rem;
  border-radius: var(--v-radius-pill);
  font-size: 0.72rem;
  font-weight: var(--v-weight-bold);
  white-space: nowrap;
}
.vs-tag--lg {
  gap: 0.7rem;
  padding: var(--v-space-4) 1.6rem;
  border-radius: var(--v-radius-md);
  font-size: 0.95rem;
  font-weight: var(--v-weight-semibold);
}
.vs-tag--lg .vs-ico { font-size: 1.3rem; line-height: 1; }

.vs-tag--primary   { background: var(--v-primary-bg);      color: var(--v-primary); }
.vs-tag--secondary { background: rgba(20, 12, 43, .08);    color: var(--v-secondary); }
.vs-tag--info      { background: var(--v-info-bg);         color: var(--v-info-ink); }
.vs-tag--success   { background: var(--v-success-bg);      color: var(--v-success-ink); }
.vs-tag--warning   { background: var(--v-warning-bg);      color: var(--v-warning-ink); }
.vs-tag--error     { background: var(--v-error-bg);        color: var(--v-error-ink); }

/* Étiquette cliquable, accent rose. Sert à la mention « gratuit pour les
   associations étudiantes ». */
.vs-tag-link {
  display: inline-flex;
  align-items: center;
  gap: var(--v-space-2);
  padding: var(--v-space-2) 1.05rem;
  border-radius: var(--v-radius-pill);
  background: var(--v-error-bg);
  color: var(--v-pink-ink);
  font-size: 0.9rem;
  font-weight: var(--v-weight-semibold);
  text-decoration: none;
  transition: background-color var(--v-duration-1) var(--v-ease);
}
.vs-tag-link:hover { background: rgba(255, 102, 146, .18); color: var(--v-pink-ink); }
.vs-tag-link--sm { padding: 0.22rem 0.6rem; font-size: 0.74rem; align-self: flex-start; }


/* ══════════════════════════════════════════════════════════════════════════
   FORMULAIRES
   ══════════════════════════════════════════════════════════════════════════ */
.form-label {
  color: var(--v-secondary);
  font-weight: var(--v-weight-semibold);
  margin-bottom: 0.35rem;
}
.form-control,
.form-select {
  border-radius: var(--v-radius-sm);
  border-color: var(--v-border);
  padding: 0.55rem 0.85rem;
  color: var(--v-text);
}
.form-control::placeholder { color: var(--v-muted); opacity: .8; }
.form-control:focus,
.form-select:focus {
  border-color: var(--v-primary);
  box-shadow: var(--v-focus-ring);
}
/* Un champ en erreur se signale par la couleur ET par un message : la couleur
   seule ne dit rien à qui ne la perçoit pas. */
.form-control.is-invalid { border-color: var(--v-error-ink); }
.vs-field-error {
  display: block;
  margin-top: var(--v-space-1);
  color: var(--v-error-ink);
  font-size: var(--v-text-sm);
  font-weight: var(--v-weight-medium);
}
.vs-field-help {
  display: block;
  margin-top: var(--v-space-1);
  color: var(--v-muted);
  font-size: var(--v-text-sm);
}

/* Champ portant une icône d'état à l'intérieur, à droite. Le retrait empêche
   la saisie de passer sous l'icône. */
.vs-field-icon-wrap { position: relative; }
.vs-field-icon-wrap .form-control { padding-right: 2.4rem; }
.vs-field-icon {
  position: absolute;
  top: 50%;
  right: var(--v-space-2);
  transform: translateY(-50%);
}


/* ══════════════════════════════════════════════════════════════════════════
   EN-TÊTE
   ══════════════════════════════════════════════════════════════════════════ */
.vs-header {
  position: sticky;
  top: 0;
  z-index: var(--v-z-sticky);
  background: var(--v-surface);
  border-bottom: 1px solid var(--v-border);
  transition: background-color var(--v-duration-3) var(--v-ease),
              border-color var(--v-duration-3) var(--v-ease);
}

/* ── EN-TÊTE POSÉ SUR LE HERO ────────────────────────────────────────────
   Transparent tant qu'on n'a pas défilé, blanc dès le premier pixel. La classe
   .is-scrolled est posée en JS, et aussi quand le menu mobile s'ouvre : sinon
   ses entrées blanches se retrouveraient sur la photo.

   L'en-tête reste dans le FLUX, il n'est pas en position:fixed. C'est le hero
   qui remonte dessous. Les autres pages ne bougent donc pas d'un pixel, et
   aucune page future ne démarrera cachée derrière lui. */
.vs-header--over-hero:not(.is-scrolled) {
  background: transparent;
  border-bottom-color: transparent;
}
.vs-header--over-hero:not(.is-scrolled) .nav-link,
.vs-header--over-hero:not(.is-scrolled) .navbar-toggler { color: var(--v-on-dark); }
.vs-header--over-hero:not(.is-scrolled) .nav-link:hover,
.vs-header--over-hero:not(.is-scrolled) .nav-link:focus { color: var(--v-on-dark); }
/* Le rose lisible sur blanc devient illisible sur la photo : on reprend le
   rose de marque, qui n'est admis que sur fond sombre. */
.vs-header--over-hero:not(.is-scrolled) .nav-link.vs-nav-accent { color: var(--v-pink); }
/* Sur la photo, le lien-bouton passe en blanc comme les nav-link voisins : le
   violet du survol y serait illisible, seul le trait marque alors le survol. */
.vs-header--over-hero:not(.is-scrolled) .vs-btn-link {
  --bs-btn-color: var(--v-on-dark);
  --bs-btn-hover-color: var(--v-on-dark);
  --bs-btn-active-color: var(--v-on-dark);
}
/* Le contour violet du bouton secondaire disparait sur une photo sombre. */
.vs-header--over-hero:not(.is-scrolled) .btn-outline-primary {
  --bs-btn-color: var(--v-on-dark);
  --bs-btn-border-color: rgba(255, 255, 255, .55);
  --bs-btn-hover-color: var(--v-secondary);
  --bs-btn-hover-bg: var(--v-on-dark);
  --bs-btn-hover-border-color: var(--v-on-dark);
}

.vs-header .navbar { padding-block: 0.85rem; }
.vs-header .btn { white-space: nowrap; }

.vs-header__logo {
  display: block;
  height: 39px;
  width: auto;
  /* CORRECTION OPTIQUE. Le mot « Ventilo » est une anglaise : la masse de son
     trace se concentre dans le bas du fichier - centre de gravite de l'encre a
     61 % de la hauteur, mesure sur le fichier meme. Centre geometriquement, le
     logo parait donc pose trop bas a cote du menu et du bouton, qui sont eux
     symetriques. On le remonte de 3 px, un peu moins que les 4,4 px d'ecart
     reel : corriger a fond ferait basculer l'erreur de l'autre cote.

     translate et non margin : la correction ne doit pas decaler la boite, donc
     ni la hauteur de l'en-tete, ni l'alignement du menu qui suit. */
  transform: translateY(-3px);
}

/* Deux logos, un par etat : le violet ne se lit pas sur une photo sombre.
   ATTENTION A L'ORDRE : ces regles ont la meme specificite que la ligne
   ci-dessus, elles DOIVENT donc venir apres. Placees avant, les deux logos
   s'affichaient l'un sous l'autre et l'en-tete faisait 116 px au lieu de 77. */
.vs-header__logo--light { display: none; }
.vs-header--over-hero:not(.is-scrolled) .vs-header__logo--dark { display: none; }
.vs-header--over-hero:not(.is-scrolled) .vs-header__logo--light { display: block; }

.vs-header .nav-link {
  font-weight: var(--v-weight-semibold);
  color: var(--v-text);
  padding-inline: 0.6rem;
  white-space: nowrap;
}
/* Survol : la couleur passe au violet ET le libelle se souligne, comme le mur
   d'applications et les vignettes metiers. Meme ecart et meme epaisseur que la
   page courante ci-dessous : dans une barre de navigation, un seul trait. */
.vs-header .nav-link:hover,
.vs-header .nav-link:focus {
  color: var(--v-primary);
  text-decoration: underline;
  text-underline-offset: 6px;
  text-decoration-thickness: 1px;
}

/* « Dans une barre de navigation, un seul trait » : le lien-bouton reprend
   l'écart des nav-link qui le précèdent, pas celui du reste du site. */
.vs-header .vs-btn-link:hover,
.vs-header .vs-btn-link:focus-visible { text-underline-offset: 6px; }

/* Page courante : soulignée. L'indication ne repose pas sur la seule couleur.
   MEME EPAISSEUR que le trait de survol (user, 04/09/2026) : deux traits de
   graisses differentes dans la meme barre se lisaient comme deux composants.
   Ce qui distingue la page courante, c'est que son trait ne s'efface pas. */
.vs-header .nav-link.active {
  text-decoration: underline;
  text-underline-offset: 6px;
  text-decoration-thickness: 1px;
}

/* Entrée mise en avant. Rose lisible, pas le rose décoratif. */
.vs-header .nav-link.vs-nav-accent,
.vs-header .nav-link.vs-nav-accent:hover,
.vs-header .nav-link.vs-nav-accent:focus { color: var(--v-pink-ink); }

/* TEXTE EN OR. Le libelle est peint par un degrade diagonal, decoupe a la
   forme des lettres (background-clip: text) et le texte lui-meme rendu
   transparent pour le laisser voir.

   Le degrade part de l'or SOMBRE et non de l'or vif : sur blanc, l'or vif
   tombe a 2:1 de contraste, illisible en texte. Le depart sombre porte la
   lecture, la fin claire porte l'eclat.

   Classe autonome, pas un style d'en-tete : elle sert aussi a la mention
   « Gratuit pour les associations etudiantes » de la page tarifs. Un meme
   geste graphique, un seul endroit ou le regler.

   -webkit- est indispensable, y compris sur Chrome et Edge : la version non
   prefixee de background-clip n'est pas reconnue partout pour la valeur
   `text`. */
.vs-or {
  background-image: linear-gradient(135deg, var(--v-gold-ink) 0%, #c9891c 45%, var(--v-gold) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  text-decoration-color: var(--v-gold-ink);
}
.vs-or:hover,
.vs-or:focus { color: transparent; }
/* L'icone reprend une COULEUR REELLE : un SVG en currentColor heriterait du
   transparent qui laisse voir le degrade, il serait donc invisible. Elle est en
   or vif la ou le texte part d'un or sombre - l'etoile n'a pas a etre lue, elle
   a a briller. */
.vs-or .vs-ico { color: var(--v-gold); font-size: 1.15em; }

/* Entree de navigation en or : la classe .vs-or fait le degrade, il ne reste
   ici que le poids, la mise en ligne de l'icone... et la transparence du texte,
   qu'il faut REPRENDRE a ce niveau de specificite : `.vs-header .nav-link`
   impose une couleur, et elle pese plus lourd qu'une classe seule. Sans ca, le
   texte reste violet et le degrade ne se voit pas. */
.vs-header .nav-link.vs-nav-vitamine,
.vs-header .nav-link.vs-nav-vitamine:hover,
.vs-header .nav-link.vs-nav-vitamine:focus {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-weight: var(--v-weight-black);
  color: transparent;
}

/* Sur la photo du hero, le probleme s'inverse : c'est l'or SOMBRE qui
   disparait. Le degrade repart donc de l'or vif vers un or clair. */
.vs-header--over-hero:not(.is-scrolled) .nav-link.vs-or {
  background-image: linear-gradient(135deg, var(--v-gold) 0%, #ffe2a3 100%);
  color: transparent;
  text-decoration-color: var(--v-gold);
}

/* BUREAU : la navigation est collee au LOGO, a gauche (user, 04/09/2026), et
   l'action reste seule a droite. Trois colonnes, mais les deux premieres au
   plus juste (auto) et la troisieme qui prend tout le reste (1fr) : c'est elle
   qui repousse le bouton au bord, sans qu'aucune marge soit calculee.

   Le menu etait centre sur l'axe de la page auparavant, ce qui demandait deux
   colonnes exterieures de 1fr. Ce n'est plus le cas : la lecture part du logo
   et continue dans le menu, d'un seul tenant.

   display:contents fait remonter le <ul> et le groupe de boutons comme
   enfants directs de la grille, alors qu'ils sont imbriques dans le
   .navbar-collapse dont Bootstrap a besoin. La regle vit dans la requete
   >= 992 px, la ou la navbar n'est justement plus repliable : le repli mobile
   n'est pas touche. */
@media (min-width: 992px) {
  .vs-header .navbar > .container {
    display: grid;
    grid-template-columns: auto auto 1fr;
    align-items: center;
    column-gap: var(--v-space-6);
  }
  .vs-header .navbar-collapse { display: contents !important; }
  .vs-header .navbar-nav { column-gap: 0.35rem; justify-content: flex-start; }
  .vs-header__actions { justify-content: flex-end; }
}
@media (max-width: 991.98px) { .vs-header .navbar-collapse { padding-top: var(--v-space-4); } }

/* Mobile uniquement : logo CENTRE dans la barre du haut, burger a droite. Le
   brand sort du flux (absolu, centre horizontalement) ; le reste de la navbar
   Bootstrap (repli du menu) est inchange. */
@media (max-width: 991.98px) {
  .vs-header .navbar > .container { position: relative; min-height: 39px; }
  .vs-header .navbar-brand {
    position: absolute;
    left: 50%;
    top: 0;
    transform: translateX(-50%);
    height: 39px;
    margin: 0;
    display: flex;
    align-items: center;
  }
  .vs-header .navbar-toggler { margin-left: auto; }
}


/* ══════════════════════════════════════════════════════════════════════════
   MÉGA-MENU
   ══════════════════════════════════════════════════════════════════════════
   SEUL usage légitime de --v-overlay-shadow depuis le retrait du bouton
   « retour en haut » (user, 04/09/2026) : le panneau flotte réellement
   au-dessus du contenu. Le site n'a pas d'autre ombre. */
.vs-megamenu {
  min-width: min(660px, 94vw);
  margin-top: var(--v-space-2);
  padding: var(--v-space-2);
  border: 1px solid var(--v-border);
  border-radius: 14px;
  box-shadow: var(--v-overlay-shadow);
}
.vs-megamenu__item {
  display: flex;
  align-items: flex-start;
  gap: var(--v-space-3);
  height: 100%;
  padding: 0.85rem var(--v-space-4);
  border-radius: var(--v-radius-xs);
  color: var(--v-secondary);
  text-decoration: none;
}
/* Survol : le TITRE de l'entree se souligne, et RIEN D'AUTRE (user,
   03/09/2026) - plus d'aplat de fond. La description ne prend pas le trait :
   c'est le titre qui nomme la destination. */
.vs-megamenu__item:hover .vs-megamenu__txt strong {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
}

/* Une largeur en plus de celle par defaut : --wide porte les 9 modules sur
   trois colonnes. */
.vs-megamenu--wide { min-width: min(760px, 94vw); }

.vs-megamenu__ico { flex: 0 0 auto; margin-top: 2px; font-size: 1.4rem; line-height: 1; }
/* Les tons -ink, jamais les tons d'aplat : une pastille de menu est du petit
   texte colore, elle doit passer le contraste sur blanc. */
.vs-megamenu__ico--primary { color: var(--v-primary); }
.vs-megamenu__ico--info    { color: var(--v-info-ink); }
.vs-megamenu__ico--success { color: var(--v-success-ink); }
.vs-megamenu__ico--warning { color: var(--v-warning-ink); }
.vs-megamenu__ico--pink    { color: var(--v-pink-ink); }

/* Entree sans destination : visible, lisible, mais inerte. C'est un <span>,
   donc ni focus clavier ni lien mort ; le survol ne fait rien pour ne pas
   promettre un clic qui n'arrivera pas. */
.vs-megamenu__item--soon { cursor: default; }
.vs-megamenu__item--soon:hover .vs-megamenu__txt strong { text-decoration: none; }
.vs-megamenu__item--soon .vs-megamenu__txt strong,
.vs-megamenu__item--soon .vs-megamenu__ico { color: var(--v-muted); }
.vs-megamenu__soon {
  margin-left: var(--v-space-2);
  padding: 0.1rem 0.4rem;
  border-radius: var(--v-radius-pill);
  background: var(--v-bg);
  color: var(--v-muted);
  font-size: 0.68rem;
  font-style: normal;
  font-weight: var(--v-weight-semibold);
  letter-spacing: var(--v-tracking-label);
  text-transform: uppercase;
  vertical-align: middle;
}

.vs-megamenu__txt { display: flex; flex-direction: column; gap: 2px; }
.vs-megamenu__txt strong { font-size: 0.95rem; font-weight: var(--v-weight-bold); }
.vs-megamenu__txt small  { color: var(--v-muted); font-size: 0.8rem; line-height: 1.3; }

.vs-megamenu-toggle .dropdown-toggle::after { margin-left: 0.35rem; }

/* Bureau : ouverture au survol. Le ::before comble l'écart créé par le
   margin-top, sinon traverser le vide referme le menu. */
@media (min-width: 992px) {
  .vs-megamenu-toggle:hover > .dropdown-menu { display: block; }

  .vs-megamenu::before {
    content: '';
    position: absolute;
    inset-inline: 0;
    top: -0.6rem;
    height: 0.6rem;
  }
}
/* Mobile : le menu s'aligne dans la navbar repliée, sans cadre ni ombre. */
@media (max-width: 991.98px) {
  .vs-megamenu { min-width: 0; margin-top: 0; padding: 0 0 var(--v-space-2); border: 0; box-shadow: none; }
  .vs-megamenu__item { padding: 0.6rem var(--v-space-2); }
}


/* ══════════════════════════════════════════════════════════════════════════
   HERO
   ══════════════════════════════════════════════════════════════════════════
   Photo pleine largeur, voile de lisibilité, contenu contraint et aligné sur
   le conteneur, donc sur le logo. */
.vs-hero {
  position: relative;
  padding-block: 6.5rem;
  color: var(--v-on-dark);
  background-color: var(--v-secondary);
}
.vs-hero h1 { color: var(--v-on-dark); }

/* Hero plein écran : la photo occupe exactement ce que le device affiche.
   svh et non vh : sur mobile, vh se cale sur le viewport BARRE D'URL MASQUÉE,
   donc la section dépasse à l'arrivée puis se recadre au premier défilement.
   svh prend le viewport barre visible, il n'y a plus de saut.
   min-height et non height : si le contenu est plus haut que l'écran, il
   pousse la section au lieu de déborder. */
.vs-hero--full {
  display: flex;
  align-items: center;
  /* Le hero remonte de la hauteur de l'en-tete et occupe l'ecran entier ; son
     padding haut compense pour que le contenu ne passe pas dessous. */
  margin-top: calc(-1 * var(--v-header-h));
  min-height: 100vh;      /* repli navigateurs anciens */
  min-height: 100svh;
  padding-top: calc(var(--v-section-y) + var(--v-header-h));
  padding-bottom: var(--v-section-y);
}
.vs-hero--full > .container { width: 100%; }

/* La photo est une vraie balise <img>, pas un background CSS : le navigateur la
   découvre dans le HTML au lieu d'attendre le parsing de la feuille de style,
   et elle peut donc porter fetchpriority="high". C'est l'image LCP de la page. */
.vs-hero__media {
  position: absolute;
  inset: 0;
  z-index: var(--v-z-base);
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* 46rem et non 42 : a 42, une accroche de six mots en 3,6 rem se cassait sur
   TROIS lignes alors que le titre n'en prevoit que deux, coupees au <br>. La
   borne existe pour que le texte ne traverse pas la photo de bord a bord ; elle
   doit rester au-dessus de la plus longue des deux lignes, pas en dessous. */
.vs-hero__content { position: relative; z-index: var(--v-z-content); max-width: 46rem; }
.vs-hero__lead {
  font-size: var(--v-text-lead);
  color: var(--v-on-dark-muted);
  max-width: var(--v-measure-tight);
}

/* Voile. Dégradé sur grand écran pour laisser respirer la photo à droite,
   uniforme sur mobile où le sujet passe sous le texte quel que soit le
   cadrage. */
/* Voile UNIFORME et NEUTRE (user, 03/09/2026). Deux choix explicites :
   - pas de degrade, la photo est assombrie de facon egale ;
   - du noir et non le violet de marque, qui teintait toute l'image. Le noir
     baisse la luminosite sans deplacer les teintes.
   Ce n'est pas un effet mais une condition de lisibilite : du texte blanc pose
   sur une photo claire descend sous le seuil de contraste. */
.vs-hero__veil {
  position: absolute;
  inset: 0;
  z-index: var(--v-z-overlay);
  background: rgba(0, 0, 0, .5);
}
/* Voile de COIN : un noir tres doux ancre dans l'angle bas gauche, eteint
   avant le milieu de l'image. Il ne sert qu'a poser un fond sous le texte, qui
   vit dans ce coin ; ailleurs la photo garde ses couleurs pleines.

   Un radial-gradient et non un lineaire : le lineaire assombrit toute une
   diagonale d'un coin a l'autre, alors que le halo se concentre autour du
   point qu'on lui donne. */
.vs-hero__veil--coin {
  background: radial-gradient(125% 110% at 0% 100%,
    rgba(0, 0, 0, .78) 0%,
    rgba(0, 0, 0, .50) 34%,
    rgba(0, 0, 0, 0) 68%);
}

.vs-hero__veil--strong {
  background: linear-gradient(90deg,
    rgba(20, 12, 43, .90) 0%,
    rgba(20, 12, 43, .60) 40%,
    rgba(20, 12, 43, .18) 46%,
    rgba(20, 12, 43, 0) 100%);
}
@media (max-width: 767.98px) {
  /* Le voile uniforme de base ne se renforce PLUS sur mobile (user,
     04/09/2026) : la photo de la landing etudiante y perdait ses couleurs.
     Elle garde donc son voile de bureau, rgba(0, 0, 0, .5). */
  .vs-hero__veil--strong { background: rgba(20, 12, 43, .78); }
  /* Sur mobile le texte couvre tout l'ecran, pas seulement un coin : le halo
     de coin ne suffirait plus, on reprend un voile uniforme mais plus leger
     que celui d'une photo claire. */
  .vs-hero__veil--coin { background: rgba(0, 0, 0, .55); }
}

/* Mention de réassurance sous les boutons. */
.vs-trust { color: var(--v-muted); font-weight: var(--v-weight-semibold); font-size: 0.92rem; }
.vs-hero .vs-trust { color: var(--v-on-dark-muted); }

/* Les deux magasins dans le hero : ils y remplacent les boutons sous 768px
   (quand HERO_STORES_MOBILE, dans bootstrap.php, les laisse s'afficher : coupes
   tant que la nouvelle version de l'app n'est pas sur les magasins, 07/09/2026).
   EMPILES et PLUS GRANDS que les badges d'une carte (user, 04/09/2026) : ici ils
   sont l'appel a l'action principal de l'ecran, pas une mention en pied de
   carte. Leur hauteur passe donc au-dessus de celle d'un bouton : ils sont ce
   qu'on vient chercher sur cet ecran, ils doivent se voir comme tels.
   Centres quand le hero l'est (page etudiante), a gauche sinon - ils suivent le
   texte au-dessus d'eux plutot que d'imposer leur propre alignement. */
.vs-hero__stores {
  flex-direction: column;
  align-items: flex-start;
}
.vs-hero__stores .vs-store img { height: 4rem; }
/* Le badge Google est SERVI plus grand a hauteur egale : son dessin remplit
   davantage son cadre que celui d'Apple. On le descend d'un cran pour que les
   deux pesent pareil a l'oeil - c'est un ajustement OPTIQUE, pas une mesure. */
.vs-hero__stores .vs-store--play img { height: 3.75rem; }
.vs-hero--centre-mobile .vs-hero__stores { align-items: center; }

/* Mobile : les deux appels à l'action passent pleine largeur et s'empilent,
   sinon ils sortent de largeurs inégales avec le « ou » collé à droite. */
@media (max-width: 991.98px) { .vs-hero { padding-top: var(--v-space-14); } }
@media (max-width: 575.98px) {
  .vs-hero__cta { flex-direction: column; align-items: stretch; gap: var(--v-space-3); }
  .vs-hero__cta .btn { width: 100%; }
}

/* HERO CENTRE SUR MOBILE (user, 04/09/2026). Sur telephone le contenu occupe
   toute la largeur : le calage a gauche n'ancre plus rien, il laisse juste des
   fins de lignes irregulieres. Centre, le bloc titre + chapeau + boutons se
   lit comme une affiche.
   Le chapeau porte une largeur de lecture (--v-measure-tight) : sans
   margin-inline auto il resterait colle a gauche, centre a l'interieur d'une
   boite elle-meme decalee. */
@media (max-width: 767.98px) {
  .vs-hero--centre-mobile .vs-hero__content { text-align: center; }
  .vs-hero--centre-mobile .vs-hero__lead    { margin-inline: auto; }
  .vs-hero--centre-mobile .vs-hero__cta     { justify-content: center; }
}


/* ══════════════════════════════════════════════════════════════════════════
   BANDEAU D'APPEL À L'ACTION
   ══════════════════════════════════════════════════════════════════════════
   Photo de marque, voile uniforme, texte blanc. Sans ombre : il se détache
   déjà par sa couleur, une ombre n'ajouterait qu'un effet. */
/* Photo + voile uniforme : le texte blanc reste lisible et les couleurs du
   visuel restent pleines. La couleur de repli s'affiche tant que l'image n'est
   pas chargée, le texte n'est donc jamais illisible.

   Cadrage au CENTRE et non en bas (user, 04/09/2026) : la photo de sport a son
   sujet au milieu, l'ancien fond avait le sien en bas. */
.vs-cta {
  padding: var(--v-space-12) var(--v-space-10);
  border-radius: var(--v-radius-lg);
  color: var(--v-on-dark);
  box-shadow: none;
  background:
    linear-gradient(rgba(26, 21, 42, .25), rgba(26, 21, 42, .60)),
    var(--v-primary-active)
    url('https://cdn.ventilo.io/ventilo_content/io/assets/src/image/sport.webp')
    center / cover no-repeat;
}
.vs-cta h2 { color: var(--v-on-dark); }
.vs-cta .btn-light { color: var(--v-primary); font-weight: var(--v-weight-bold); }


/* ══════════════════════════════════════════════════════════════════════════
   BASCULE
   ══════════════════════════════════════════════════════════════════════════
   Un segment : deux ou trois options dans un meme cadre, une seule allumee.
   Sert a la periodicite de /tarifs/ (Annuel / Mensuel) et aux onglets du
   parcours de l'accueil - un seul composant, donc une seule mecanique a
   comprendre pour le visiteur.

   L'etat actif est porte par aria-selected et non par une classe : c'est
   l'attribut que posent DEJA les deux implementations (le petit script de
   /tarifs/ comme les onglets Bootstrap), et c'est celui que lit un lecteur
   d'ecran. Une classe en plus serait une deuxieme source de verite, qui peut
   se desynchroniser de la premiere. */
/* La coque vaut le rayon d'une option PLUS son retrait : c'est la seule facon
   d'obtenir deux arrondis concentriques. Elle suit donc le rayon des commandes,
   elle ne le fixe pas. */
.vs-switch {
  display: inline-flex;
  /* La bascule NE PASSE PAS A LA LIGNE : elle DEFILE (user, 04/09/2026). Sur
     une largeur de telephone, quatre onglets se repliaient en deux rangs, et un
     segment sur deux rangs ne se lit plus comme un segment - il se lit comme
     deux groupes de boutons. Un rang unique qu'on pousse du doigt garde la
     forme de l'objet et laisse acceder a tous les onglets.
     overscroll-behavior : sans elle, arriver au bout du rang entraine le
     geste de retour arriere du navigateur. */
  flex-wrap: nowrap;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  /* Barre de defilement masquee : dans une pastille de 4px de retrait, elle se
     lirait comme un defaut d'affichage. L'onglet coupe au bord suffit a dire
     que le rang continue. */
  scrollbar-width: none;
  gap: 4px;
  max-width: 100%;
  padding: 4px;
  border-radius: calc(var(--v-radius-btn) + 4px);
  border: 1px solid var(--v-border);
  background: var(--v-surface);
}
.vs-switch::-webkit-scrollbar { display: none; }
.vs-switch__opt {
  /* Ni retrecissement ni retour a la ligne : c'est ce qui fait defiler le rang
     au lieu de le comprimer. */
  flex: 0 0 auto;
  white-space: nowrap;
  padding: var(--v-space-2) 1.75rem;
  border: 0;
  /* Rayon d'une commande de taille normale : une option de bascule en est une
     (user, 04/09/2026). */
  border-radius: var(--v-radius-btn);
  background: transparent;
  color: var(--v-muted);
  font-weight: var(--v-weight-medium);
  cursor: pointer;
  transition: background-color var(--v-duration-2) var(--v-ease),
              color var(--v-duration-2) var(--v-ease);
}
.vs-switch__opt:hover { background: rgba(78, 66, 106, .06); }
.vs-switch__opt[aria-selected="true"],
.vs-switch__opt[aria-selected="true"]:hover {
  background: var(--v-primary-bg);
  color: var(--v-primary);
  font-weight: var(--v-weight-semibold);
}

/* Variante TRAIT : l'option allumee prend la couleur du chemin qu'elle ouvre,
   dans le meme ordre de rang. L'onglet et sa frise se repondent, on voit d'un
   coup lequel des quatre temps on regarde.

   Modificateur et non regle globale : la meme bascule sert la periodicite de
   /tarifs/, qui n'a pas de chemin et doit rester violette. Le texte passe en
   nuit - sur ces pastels, le violet de marque ne tient plus le contraste. */
.vs-switch--trait > :nth-child(1)[aria-selected="true"] { background: var(--v-trait-1); }
.vs-switch--trait > :nth-child(2)[aria-selected="true"] { background: var(--v-trait-2); }
.vs-switch--trait > :nth-child(3)[aria-selected="true"] { background: var(--v-trait-3); }
.vs-switch--trait > :nth-child(4)[aria-selected="true"] { background: var(--v-trait-4); }
.vs-switch--trait > [aria-selected="true"] { color: var(--v-secondary); }


/* ══════════════════════════════════════════════════════════════════════════
   PARCOURS (onglets)
   ══════════════════════════════════════════════════════════════════════════
   Trois onglets, trois volets de structure IDENTIQUE : photo, titre, texte,
   puis les quatre pas de l'acte. Rien ne doit apparaitre dans un volet et pas
   dans un autre, sinon le changement d'onglet se lit comme un changement de
   section et la chronologie se perd.

   Les douze pas sont numerotes en continu d'un onglet a l'autre, portes par
   des <ol start> qui se suivent : l'ordre est une information, il doit exister
   pour un lecteur d'ecran comme pour l'oeil. */
/* Le volet ne porte plus QUE les trois pas (user, 03/09/2026) : ni image, ni
   titre, ni chapeau. Le nom de l'onglet dit deja de quoi il s'agit, le repeter
   dans le volet le disait deux fois. */
/* Trois pas, trois colonnes. Le meme nombre dans chaque onglet : c'est ce qui
   permet cette grille fixe, et ce qui empeche la page de sauter au changement
   d'onglet. */
/* L'ecart entre la bascule et le volet vaut LE PADDING DE LA SECTION, le meme
   qui ferme la section sous le dernier pas (user, 04/09/2026). Il valait 8rem,
   soit plus du double : le volet flottait loin sous ses onglets puis s'arretait
   net en bas, et le desequilibre se voyait.
   Cette valeur avait ete posee pour loger les cretes de la courbe, qui
   sortaient alors du volet par le haut ; le trace a change depuis et tient
   entierement dans sa boite - mesure a 0 px de debord. Le token suit aussi les
   largeurs, la regle mobile qui le corrigeait n'a donc plus lieu d'etre. */
.vs-path__flow {
  --v-halo: var(--v-surface);
  position: relative;
  margin-top: var(--v-section-y);
}
/* Seule la couleur du halo depend du fond qui porte le bloc : un modificateur
   pour ca seul serait plus lourd que ce selecteur de contexte. */
.vs-section--tinted .vs-path__flow { --v-halo: var(--v-bg); }
.vs-path__steps {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--v-space-6);
  padding: 0;
  margin: 0;
  list-style: none;   /* le repere est l'icone, cf. .vs-path__icon */
}

/* Le chemin d'une etape a la suivante. Une courbe de Bezier posee DERRIERE les
   pastilles, sur la bande qu'elles occupent (meme hauteur qu'elles) : elle
   passe donc par leur centre, et leur fond opaque la coupe proprement de part
   et d'autre. C'est du decor - l'ordre est deja dit par le <ol> et par les
   numeros - donc rien a annoncer et rien a cliquer. */
.vs-path__line {
  position: absolute;
  inset: 0 0 auto;
  width: 100%;
  height: 6rem;       /* la bande des icones : y=48 tombe sur leur centre */
  /* Les cretes de la courbe sortent de la boite par le haut, dans le vide
     laisse sous le chapeau. Rien a couper : c'est voulu. */
  overflow: visible;
  pointer-events: none;
  color: var(--v-primary);
}
/* VARIANTE COLOREE, posee sur le conteneur des volets : une couleur de trait
   par acte, prise sur le RANG du volet. Le chemin change de teinte a chaque
   onglet, ce qui montre qu'on a avance dans le recit sans rien ecrire de plus.

   Modificateur et non regle globale : la coloration reste un choix de page -
   les deux landing pages la portent, une future page pourra garder le trait
   violet uni. Par rang et non par classe posee sur chaque volet : l'ordre des
   actes est deja dans le HTML, il n'a pas a etre redit. */
.vs-path--trait > :nth-child(1) .vs-path__line { color: var(--v-trait-1); }
.vs-path--trait > :nth-child(2) .vs-path__line { color: var(--v-trait-2); }
.vs-path--trait > :nth-child(3) .vs-path__line { color: var(--v-trait-3); }
.vs-path--trait > :nth-child(4) .vs-path__line { color: var(--v-trait-4); }

.vs-path__line path {
  fill: none;
  stroke: currentColor;
  /* Trait EPAIS : un ruban, pas une rayure. La courbe ne passant plus derriere
     aucun texte, il peut porter un peu plus sans rien gener. */
  stroke-width: 10;
  stroke-linecap: round;
  /* Le violet doit etre ATTENUE pour ne pas dominer la frise. Les pastels de la
     variante coloree, eux, sont deja clairs : les diluer les ferait
     disparaitre, ils passent donc en pleine opacite juste en dessous. */
  opacity: .22;
  /* L'epaisseur est en pixels d'ecran et non en unites du viewBox : sans cela
     l'etirement horizontal l'ecraserait dans un sens et l'epaissirait dans
     l'autre. */
  vector-effect: non-scaling-stroke;
}
.vs-path__step {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--v-space-2);
  text-align: center;
}

/* L'icone qui repere l'etape sur la courbe. Registre Iconly 3D : une image,
   jamais teintee (cf. le mur d'applications), a une taille ou le relief se
   voit vraiment.

   Elle est POSEE SUR la courbe, sans aucun fond (user, 03/09/2026) : le trait
   n'est donc plus coupe, il passe librement derriere l'illustration et se voit
   dans ses transparences. Le z-index reste necessaire - le SVG est positionne,
   il passerait sinon par-dessus l'image. */
.vs-path__icon {
  position: relative;
  z-index: var(--v-z-raised);
  display: block;
  width: 6rem;
  height: 6rem;
  object-fit: contain;
  /* Halo (user, 03/09/2026). drop-shadow et non box-shadow : il epouse la FORME
     de l'illustration (son canal alpha) au lieu de cerner son cadre carre. Deux
     passes : la premiere pose une lueur large, la seconde la densifie au
     contact du dessin. La courbe s'efface donc en approchant de l'icone, sans
     qu'aucun disque opaque ne la coupe net.

     Sa couleur est celle de la SECTION, pas du blanc en dur : un halo blanc sur
     une section teintee se verrait comme une tache autour de chaque icone. */
  filter: drop-shadow(0 0 14px var(--v-halo))
          drop-shadow(0 0 6px var(--v-halo));
}
.vs-path--trait .vs-path__line path { opacity: 1; }

.vs-path__label { font-weight: var(--v-weight-semibold); line-height: var(--v-lh-snug); text-wrap: balance; }

@media (max-width: 767.98px) {
  /* Trois colonnes de pas deviennent illisibles : ils s'empilent, alignes a
     gauche, le numero revenant devant le libelle. */
  .vs-path__steps { grid-template-columns: 1fr; gap: var(--v-space-6); }
  /* Les pas restent CENTRES, icone au-dessus du libelle, comme sur grand ecran
     (user, 04/09/2026). Ils avaient ete passes en ligne et cales a gauche pour
     gagner de la hauteur ; le volet se lisait alors comme une liste, alors que
     c'est une frise. */
  /* Les pas s'empilent : un chemin horizontal ne relie plus rien. */
  .vs-path__line { display: none; }
}


/* ══════════════════════════════════════════════════════════════════════════
   BANDEAU DE FIN
   ══════════════════════════════════════════════════════════════════════════
   Bloc CONTENU dans le conteneur et arrondi comme un cadre média : la photo
   n'est pas une section pleine largeur, elle est posée dans le rythme de la
   page. Le montage reste celui du hero (photo en <img>, voile, contenu
   au-dessus), avec la couleur de repli sous la photo pour que le texte blanc
   reste lisible tant que l'image n'est pas arrivée.
   Texte et action sur une seule ligne : la hauteur n'est que celle du contenu
   et de son padding, aucune hauteur imposée, donc rien à recalculer si le
   texte change. */
.vs-band {
  position: relative;
  overflow: hidden;
  border-radius: var(--v-radius-lg);
  color: var(--v-on-dark);
  background-color: var(--v-secondary);
}
.vs-band__media {
  position: absolute;
  inset: 0;
  z-index: var(--v-z-base);
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}
/* Même voile neutre que le hero : du noir, qui baisse la luminosité sans
   déplacer les teintes de la photo. */
.vs-band__veil {
  position: absolute;
  inset: 0;
  z-index: var(--v-z-overlay);
  background: rgba(0, 0, 0, .55);
}
.vs-band__inner {
  position: relative;
  z-index: var(--v-z-content);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--v-space-6);
  padding: var(--v-space-12) var(--v-space-10);
}
.vs-band__text { max-width: var(--v-measure-tight); }
/* L'accroche partage sa ligne avec le bouton : elle descend donc d'un cran par
   rapport a un H2 de section, la taille pleine desequilibrait la bande. Ce cran
   est celui de l'ECHELLE et non une valeur a elle (user, 04/09/2026) : c'est le
   rang h3, le meme que les titres de carte. Le niveau de titre, lui, ne change
   pas - c'est bien un H2 dans le plan du document. */
.vs-band h2 {
  margin: 0;
  color: var(--v-on-dark);
  font-size: var(--v-text-h3);
}
.vs-band__lead {
  margin: var(--v-space-2) 0 0;
  color: var(--v-on-dark-muted);
}
.vs-band__cta { flex: 0 0 auto; }

@media (max-width: 767.98px) {
  .vs-band__inner { padding: var(--v-space-6); }
  .vs-band__cta,
  .vs-band__cta .btn { width: 100%; }
}


/* ══════════════════════════════════════════════════════════════════════════
   BANDEAU DÉFILANT
   ══════════════════════════════════════════════════════════════════════════
   Décor. Il ne doit JAMAIS porter la seule occurrence d'une information : ce
   qui défile est illisible pour une partie des visiteurs et ne se sélectionne
   pas. Le défilement s'arrête au survol, et la garde globale de base.css le
   fige pour qui limite les animations.

   La piste avance d'exactement LA MOITIÉ de sa largeur : la liste étant
   doublée dans le HTML, la seconde moitié se retrouve alors pile là où la
   première commençait, et la boucle ne se voit pas. Toute autre valeur fait
   sauter la bande au retour à zéro. */
.vs-marquee {
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}
.vs-marquee__track {
  display: flex;
  gap: var(--v-space-4);
  width: max-content;
  padding: 0;
  margin: 0;
  list-style: none;
  animation: vs-defile 90s linear infinite;
}
.vs-marquee:hover .vs-marquee__track { animation-play-state: paused; }

.vs-marquee__item { width: 20rem; }
.vs-marquee__item img {
  display: block;
  width: 100%;
  /* height:auto est INDISPENSABLE : l'attribut height du HTML vaut la hauteur
     REELLE du fichier (4572 px pour certaines photos) et fait loi tant que le
     CSS ne le reprend pas. Sans lui, aspect-ratio ne sert a rien et chaque
     vignette fait plusieurs milliers de pixels de haut. */
  height: auto;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-radius: var(--v-radius-md);
}
.vs-marquee__item span {
  display: block;
  margin-top: var(--v-space-2);
  color: var(--v-muted);
  font-size: var(--v-text-sm);
  font-weight: var(--v-weight-semibold);
  text-align: center;
}

@keyframes vs-defile {
  from { transform: translateX(0); }
  to   { transform: translateX(calc(-50% - var(--v-space-4) / 2)); }
}


/* ══════════════════════════════════════════════════════════════════════════
   BOUTONS « DEMANDER À UNE IA »
   ══════════════════════════════════════════════════════════════════════════
   Une rangee de liens qui ouvrent un assistant avec une question deja ecrite.
   Ils sont calibres comme les boutons du site - meme hauteur, meme rayon, meme
   padding horizontal - pour se lire comme des actions et non comme des notes
   de bas de page. */
.vs-askai {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--v-space-3);
  padding: 0;
  margin: 0;
  list-style: none;
}
.vs-askai__link {
  display: inline-flex;
  align-items: center;
  gap: var(--v-space-2);
  min-height: var(--v-btn-h);
  padding-inline: var(--v-space-5);
  border: 1px solid var(--v-border);
  border-radius: var(--v-radius-sm);
  background: var(--v-surface);
  color: var(--v-text);
  font-size: var(--v-text-sm);
  font-weight: var(--v-weight-semibold);
  line-height: 1;
  text-decoration: none;
  transition: color var(--v-duration-1) var(--v-ease),
              border-color var(--v-duration-1) var(--v-ease),
              background-color var(--v-duration-1) var(--v-ease);
}
.vs-askai__link:hover {
  color: var(--v-primary);
  border-color: var(--v-primary);
  background: var(--v-bg);
}
/* Le survol echange les deux surfaces : blanc au repos, teinte au survol. Sur
   une section deja teintee la pastille disparaitrait donc au moment ou on la
   vise. Seul composant du systeme dont l'etat de survol depend du fond qui le
   porte, d'ou ce selecteur de contexte plutot qu'un modificateur. */
.vs-section--tinted .vs-askai__link:hover { background: var(--v-surface); }
/* currentColor : la marque suit la couleur du texte, y compris au survol. */
.vs-askai__icon {
  flex-shrink: 0;
  width: 1.15em;
  height: 1.15em;
  fill: currentColor;
}


/* ══════════════════════════════════════════════════════════════════════════
   MÉDIAS
   ══════════════════════════════════════════════════════════════════════════ */
/* Logo de partenaire de paiement. Les fichiers sont des rectangles opaques
   (Stripe sur son violet de marque, HelloAsso sur blanc) : le rayon se voit
   donc vraiment, il transforme le rectangle brut en plaque. Il suit la taille
   du logo pour rester proportionne. */
.vs-logo-pay {
  height: 34px;
  width: auto;
  border-radius: var(--v-radius-sm);
  opacity: .9;
  filter: grayscale(15%);
  transition: opacity var(--v-duration-2) var(--v-ease), filter var(--v-duration-2) var(--v-ease);
}
.vs-logo-pay:hover { opacity: 1; filter: none; }
.vs-logo-pay--lg { height: 76px; border-radius: var(--v-radius-md); }
@media (max-width: 575.98px) {
  .vs-logo-pay--lg { height: 44px; border-radius: var(--v-radius-sm); }
}

/* Bande photo pleine largeur, fondue dans le fond de page. */

/* ══════════════════════════════════════════════════════════════════════════
   PIED DE PAGE
   ══════════════════════════════════════════════════════════════════════════ */
.vs-footer {
  padding: var(--v-space-16) 0 var(--v-space-8);
  background: var(--v-secondary);
  color: #cdd3de;
}
.vs-footer__tagline { color: #9aa2b4; max-width: 34ch; }

/* RESEAUX. Des liens carres, assez grands pour etre vises au doigt (44 px,
   la cible minimale recommandee), avec le trace en currentColor pour qu'il
   s'eclaircisse au survol sans qu'il y ait deux versions du fichier. */
.vs-footer__social {
  display: flex;
  gap: var(--v-space-2);
  padding: 0;
  margin: var(--v-space-5) 0 0;
  list-style: none;
}
.vs-footer__social a {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: var(--v-radius-sm);
  color: var(--v-on-dark-muted);
  font-size: 1.25rem;
  transition: color var(--v-duration-2) var(--v-ease),
              background-color var(--v-duration-2) var(--v-ease);
}
.vs-footer__social a:hover {
  color: var(--v-on-dark);
  background: rgba(255, 255, 255, .10);
}

/* Mobile uniquement : logo + baseline (tagline) + reseaux centres dans le pied.
   Sur bureau, la premiere colonne reste alignee a gauche. */
@media (max-width: 991.98px) {
  .vs-footer .row > .col-lg-4 { text-align: center; }
  .vs-footer .row > .col-lg-4 img { display: inline-block; }
  .vs-footer__tagline { margin-inline: auto; }
  .vs-footer__social { justify-content: center; }
  /* Colonnes de GAUCHE (Produit, Légal = les col-lg-3 ; Ressources est col-lg-2,
     à droite) : elles étaient collées au bord de l'écran sur mobile. On AJOUTE
     20px par-dessus la demi-gouttière Bootstrap (sinon padding-left la
     remplacerait et le décalage net serait quasi nul). */
  .vs-footer .row > .col-lg-3 { padding-left: calc(var(--bs-gutter-x, 1.5rem) * 0.5 + var(--v-space-5)); }
}

.vs-footer__title {
  margin-bottom: var(--v-space-4);
  font-size: var(--v-text-label);
  font-weight: var(--v-weight-bold);
  letter-spacing: var(--v-tracking-label);
  text-transform: uppercase;
  color: var(--v-on-dark);
}
.vs-footer__links { list-style: none; padding: 0; margin: 0; }
.vs-footer__links li { margin-bottom: 0.55rem; }
.vs-footer__links a { color: #aab1c2; text-decoration: none; }
.vs-footer__links a:hover { color: var(--v-on-dark); text-decoration: underline; }

.vs-footer__sep { border-color: rgba(255, 255, 255, .12); margin: var(--v-space-10) 0 var(--v-space-5); }
/* La ligne du bas ne porte plus qu'une mention (user, 04/09/2026) : elle est
   donc CENTREE. Le space-between d'origine repartissait plusieurs elements aux
   deux bords ; avec un seul, il le collait a gauche. */
.vs-footer__bottom {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--v-space-2);
  color: #8a92a5;
  font-size: 0.9rem;
  text-align: center;
}
/* Zone tactile plus généreuse sur mobile. */
@media (max-width: 575.98px) { .vs-footer__links a { display: inline-block; padding-block: 0.2rem; } }





/* ══════════════════════════════════════════════════════════════════════════
   ACCORDÉON  (mécanique Bootstrap)
   ══════════════════════════════════════════════════════════════════════════
   L'en-tête reste blanc une fois ouvert : le repli par défaut de Bootstrap le
   teinte en bleu, ce qui n'est pas notre palette. */
.vs-accordion .accordion-item {
  margin-bottom: var(--v-space-3);
  border: 0;
  border-radius: var(--v-radius-md);
  overflow: hidden;
  background: var(--v-surface);
}
.vs-accordion .accordion-button {
  background-color: var(--v-surface);
  color: var(--v-secondary);
  font-weight: var(--v-weight-bold);
}
.vs-accordion .accordion-button:not(.collapsed) {
  background-color: var(--v-surface);
  color: var(--v-secondary);
  box-shadow: none;
}
.vs-accordion .accordion-button:focus { box-shadow: none; border-color: var(--v-border); }
.vs-accordion .accordion-button:focus-visible { box-shadow: var(--v-focus-ring); }
.vs-accordion .accordion-body { color: var(--v-muted); }

/* Même règle que les cartes : sur une section blanche, les volets se teintent. */
.vs-accordion--tinted .accordion-item,
.vs-accordion--tinted .accordion-button,
.vs-accordion--tinted .accordion-button:not(.collapsed) { background-color: var(--v-bg); }


/* ══════════════════════════════════════════════════════════════════════════
   PROSE  (pages légales et textes longs)
   ══════════════════════════════════════════════════════════════════════════ */
.vs-prose { max-width: 52rem; }
.vs-prose h1 { margin-bottom: var(--v-space-1); }
.vs-prose__meta { margin-bottom: 2.25rem; color: var(--v-muted); font-size: 0.9rem; }
.vs-prose section { margin-bottom: 1.75rem; }
/* Les titres d'article suivent l'ECHELLE DU SITE et non des valeurs a eux
   (user, 04/09/2026) : h3 pour le rang de titre de carte, corps de texte pour
   le rang en dessous - les memes que partout ailleurs. Ils gardent en revanche
   leurs marges, qui sont celles d'un document suivi et non d'une section. */
.vs-prose h2 { margin: var(--v-space-8) 0 var(--v-space-3); font-size: var(--v-text-h3); }
.vs-prose h3 { margin: var(--v-space-5) 0 var(--v-space-2); font-size: var(--v-text-body); }
.vs-prose p  { margin-bottom: var(--v-space-3); color: var(--v-muted); line-height: var(--v-lh-loose); }
.vs-prose ul,
.vs-prose ol { margin: 0 0 var(--v-space-3) var(--v-space-5); color: var(--v-muted); line-height: var(--v-lh-loose); }
.vs-prose a  { color: var(--v-primary); }
