/* Hero Page Styles */
.hero-container { 
    min-height: 100vh; min-height: 100dvh; background: radial-gradient(circle at center, var(--bg-1), var(--panel), var(--bg-3));
    display: flex; flex-direction: column; align-items: center; justify-content: flex-start; text-align: center; padding: 0;
    /* Meme correctif que home.css et admin.css, et pour la meme raison : avec
       overflow-y:auto, body devenait un conteneur de defilement, donc le
       position:sticky du header ne se collait plus a la fenetre mais au
       scrollport de body -- il remontait avec le contenu et disparaissait.
       Ni clip ni visible ne creent de conteneur de defilement. C'est clip et
       non hidden qu'il faut : hidden en cree un aussi, et le symptome
       reviendrait.

       height:auto est indispensable et ne va pas de soi : base.css impose
       height:100vh a body, donc sa boite restait bloquee a une hauteur de
       fenetre alors que le contenu depassait. D'une part le degrade radial
       ci-dessus ne peint que ces 100vh, d'autre part la page doit pouvoir
       defiler sur le viewport -- c'est ce que le sticky attend. */
    height: auto;
    overflow-y: visible; overflow-x: clip;
}

/* Le defilement du document se fait desormais par le viewport, donc c'est
   <html> qui porte le scroll-behavior -- sur body il serait sans effet.
   Selecteur scope a l'accueil : landing.css est charge par toutes les pages,
   on ne veut pas rendre le scroll lisse sur travels.php ou travel.php sans
   qu'on l'ait demande. Sur un navigateur sans :has(), la regle est ignoree et
   les ancres #login sautent au lieu de glisser : degrade sans casse.

   scrollbar-gutter sur la meme regle, et pour une raison qui n'a rien a voir
   avec le scroll lisse : le verrou de defilement plus bas SUPPRIME la barre de
   scroll, donc tout le contenu sauterait de sa largeur a chaque ouverture de
   modale. Une gouttiere reservee en permanence rend le retrait invisible. */
html:has(> body.hero-container) { scroll-behavior: smooth; scrollbar-gutter: stable; }

/* ---------- Verrou de defilement pendant une modale ----------

   showModal() ne bloque pas le document : le fond reste defilable a la
   molette comme au doigt, et une modale de saisie qu'on fait glisser derriere
   elle donne l'impression d'avoir change de page. Le defilement se fait par le
   viewport (.hero-container est explicitement en overflow visible, voir plus
   haut), donc c'est <html> qu'il faut arreter -- et surtout pas body, dont
   l'overflow a ete choisi en clip precisement pour que le header sticky
   survive.

   :has() + :modal plutot qu'une classe posee en JS : rien a poser, donc rien
   qui puisse diverger du vrai etat de la boite. La regle disparait seule quand
   la boite se ferme, y compris par Echap ou par le retour arriere, qu'aucun
   gestionnaire ne voit passer. Ces deux selecteurs sont ignores sur un
   navigateur trop ancien : la modale reste ouverte et utilisable, seul le fond
   continue de defiler. Seules les .login-modal de index.php existent, donc
   cette regle n'atteint aucune autre page malgre le chargement de
   landing.css partout. */
html:has(.login-modal:modal) {
    overflow: hidden;
    /* Sans cela, le defilement qui n'a pas lieu d'etre se poursuit dans le
       conteneur parent : sur iOS c'est le rebond elastique, qui fait
       remonter toute la page avec le doigt. */
    overscroll-behavior: none;
}

.landing-header {
    width: 100%;
    padding: 12px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 10px;
    background: var(--black-20);
    backdrop-filter: blur(10px);
    position: sticky;
    top: 0;
    z-index: 2000;
}
.landing-header .logo { display: flex; align-items: center; gap: 9px; }
/* Globe : l'ocean et les terres sont peints dans le canvas, rien derriere.
   Pas de sphere SVG, donc pas de degrade fixe qui tache le globe. */
.logo-globe { position: relative; width: clamp(32px, 5.5vw, 42px); aspect-ratio: 1 / 1; flex-shrink: 0; }
.logo-globe__land { display: block; width: 100%; height: 100%; border-radius: 50%; }
.logo-globe--static { opacity: 0.6; }
/* declencheur de retour sur le lien de travel.php */
.back-link { display: flex; align-items: center; }
/* et, depuis travels.php / admin.php, le lien de marque lui-meme. Le
   text-decoration:none est indispensable : .logo-text est un degrade en
   text-fill-color:transparent, et un <a> souligne par defaut -- le
   soulignement se peindrait avec le texte, donc en transparent, ou pire
   en couleur de lien sur le degrade. Le .logo de .landing-header (display
   flex, gap 9px) est deja pose par cet element, rien a ajouter ici. */
a.logo { text-decoration: none; }
/* tailles selon l'emplacement */
.logo-globe--xs {width: 32px;}
.logo-globe--hero {width: clamp(114px, 50vw, 240px);margin: 0 auto clamp(18px, 4vw, 32px);}
.landing-header .logo-text { font-size: clamp(1.2rem, 5vw, 1.8rem); font-weight: 900; background: linear-gradient(to right, var(--primary-2), var(--primary)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.nav-links { display: flex; gap: clamp(10px, 3vw, 30px); align-items: center; }
.nav-links a { text-decoration: none; color: var(--text); font-weight: 600; font-size: 0.9rem; transition: color 0.3s; }
.nav-links a:hover { color: var(--primary); }
/* Ces regles sont prefixees par .nav-links, et ce n'est pas pour la forme :
   .nav-links a:hover (1,2) l'emporte sur un simple .btn-login-nav:hover (1,1).
   Sans ce prefixe, le bouton heritait donc color: var(--primary) au survol,
   sur son propre fond --primary : texte illisible. Le prefixe (2,1) passe
   devant, et l'accent se signale par la luminosite du fond, comme .back-btn,
   pour la meme raison. */
.nav-links .btn-login-nav {
    background: var(--primary); padding: 8px 20px; border-radius: 10px;
    color: #fff; transition: filter 0.3s, background 0.3s;
    box-shadow: 0 4px 15px var(--primary-30);
}
.nav-links .btn-login-nav:hover { background: var(--primary-2); color: #fff; filter: brightness(1.12); }
.nav-links .btn-login-nav:focus-visible { outline: 2px solid var(--primary-2); outline-offset: 2px; }
/* ---------- Selecteur de langue du header (page d'accueil) ----------

   Les visiteurs de la page d'accueil sont anonymes : ils n'ont pas de ligne
   dans map_user_settings, donc le selecteur ne peut pas passer par
   ajax/update_settings.php. Il pose le cookie mapify_lang et recharge, ce que
   t_lang() lit ensuite en repli du reglage.

   Volontairement sobre : un menu a drapeaux, pas un <select> natif. Le natif
   cachait "English" dans la liste, donc invisible avant ouverture, et
   s'ouvrait sur la Wheelset d'Android/iOS par-dessus la page. Ici le nom de
   la langue est visible en permanence, et le drapeau donne la position dans
   la liste. Voir includes/header_lang.php. */

/* Selecteur de langue : menu a drapeaux, sur le modele du menu compte.

   Un drapeau, rien d'autre, dans le declencheur comme dans la liste, mais
   deux presentations volontairement differentes :
   - declencheur : un DISQUE. La boite est carree, donc border-radius 50%
     donne un cercle parfait, et object-fit rogne le drapeau au centre.
   - liste : un RECTANGLE 3/2, la forme reelle d'un drapeau, lisible d'un
     coup d'oeil a cote du nom de la langue.

   Les deux tailles se reglent ici, et nulle part ailleurs. */
.lang-menu {
    position: relative;
    --flag-btn: 30px;    /* hauteur du disque du declencheur */
    --flag-item: 22px;   /* hauteur du rectangle de la liste */
}

/* Le declencheur n'est qu'un disque : ni cadre, ni fond au repos, pour qu'aucun
   carre ne depasse du rond. Le padding de 3px laisse un anneau au survol, sinon
   le drapeau couvrirait le fond et le hover ne se verrait pas. L'overflow
   rogne aussi le blanc que la ligne de base laisse sous une image dans un
   bouton, qui ferait deborder le cercle en bas. */
.lang-btn {
    display: flex; align-items: center; justify-content: center;
    background: transparent;
    border: 0;
    border-radius: 50%;
    padding: 3px;
    overflow: hidden;
    cursor: pointer;
    transition: background-color 0.2s;
}
.lang-btn:hover { background-color: var(--white-10); }
.lang-btn:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

/* Les width/height des <img> servent a reserver la place avant chargement ;
   sans eux la mise en page saute quand le SVG arrive. Le flex:none empeche
   le drapeau de se comprimer dans un header etroit. Le lisere sert a
   separer les bandes du drapeau britannique, collees sur un fond sombre. */
.lang-flag {
    display: block;
    flex: none;
    /* Carre : c'est la condition d'un disque, un ovale exigerait du 3/2. */
    width: var(--flag-btn);
    height: var(--flag-btn);
    border-radius: 50%;
    border: 1px solid var(--white-15);
    object-fit: cover;
    background: var(--bg-3);
}

.lang-menu-flag {
    display: block;
    flex: none;
    /* 3/2 comme les viewBox : ici on garde la forme du drapeau. */
    width: calc(var(--flag-item) * 3 / 2);
    height: var(--flag-item);
    border-radius: 3px;
    border: 1px solid var(--white-15);
    object-fit: cover;
    background: var(--bg-3);
}

/* Repli pour une langue sans fichier : le code en toutes lettres tient la
   place de l'image, donc la ligne ne se deforme pas. */
.lang-flag--none {
    display: flex; align-items: center; justify-content: center;
    font-family: inherit; font-size: 0.66rem; font-weight: 700;
    line-height: 1;
    color: var(--text-dim);
    background: var(--white-05);
    border-color: var(--white-10);
}

.lang-menu-panel {
    position: absolute; top: calc(100% + 6px); right: 0;
    min-width: 168px;
    background: var(--darker);
    border: 1px solid var(--white-10);
    border-radius: 10px;
    padding: 4px;
    z-index: 40;
    display: flex; flex-direction: column; gap: 2px;
    box-shadow: 0 10px 28px rgba(0, 0, 0, 0.45);
}
.lang-menu-panel[hidden] { display: none; }

.lang-menu-item {
    display: flex; align-items: center; gap: 9px;
    padding: 8px 10px;
    border-radius: 7px;
    color: var(--text);
    font-size: 0.8rem; font-weight: 600;
    text-decoration: none;
    white-space: nowrap;
    transition: background-color 0.15s, color 0.15s;
}
.lang-menu-item:hover { background: var(--white-10); color: var(--text); }
.lang-menu-item:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
.lang-menu-item.is-active { background: var(--white-10); color: var(--primary); }

@media (max-width: 480px) {
    .nav-links a { font-size: 0.78rem; }
    .nav-links .btn-login-nav { padding: 8px 12px; }
    .lang-menu-item { font-size: 0.78rem; padding: 9px 10px; }
    .login-btn--cta { padding: 14px 22px; font-size: 0.88rem; }
}

.hero-section {
    padding: clamp(48px, 11vw, 100px) 20px; display: flex; flex-direction: column;
    align-items: center; width: 100%;
    position: relative;   /* support du ciel en SVG ci-dessous */
    overflow: hidden;
}
.hero-content { max-width: 800px; position: relative; z-index: 1; }

/* --- Ciel du hero ------------------------------------------------------
   Un SVG, pas un canevas : une trentaine d'etoiles en elements statiques
   costsent au navigateur une simple animation CSS, donc rien a boucler en JS
   pendant que le globe tourne deja sur son propre rAF.

   Volontairement SANS translation. Une derive -- meme tres lente -- se lit
   comme de la poussiere qui flotte, pas comme des etoiles : les etoiles sont
   fixes a l'echelle des annees-lumiere, c'est leur LUMIERE qui varie. Seul
   opacity et un léger scale changent donc ici, ce qui donne le scintillement
   sans le glissement. */
.hero-sky {
    position: absolute; inset: 0; z-index: 0;
    width: 100%; height: 100%;
    pointer-events: none;
    /* Sans ce masque, la nappe de nebuleuse se faisait couper net par
       overflow:hidden de .hero-section : les ellipses debordent largement du
       viewBox, donc elles etaient tranchees en pleine opacite. Le ciel
       s'efface ici au lieu d'etre tranche.

       Un masque, et pas un degrade ajoute a la section suivante : la section
       n'a pas de fond propre, elleMontre le degrade du body. C'est donc bien
       le ciel qui doit se dissoudre, sinon on peindrait une bande par-dessus un
       fond qu'on ne controle pas.

       Le haut reste plein -- le hero est colle sous le header, un fondu la
       detacherait de la barre. -webkit- en tete car Safari ne connait pas
       encore mask-image seul. */
    -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 42%, transparent 100%);
    mask-image: linear-gradient(180deg, #000 0%, #000 42%, transparent 100%);
}

/* Les etoiles vivent dans le viewBox (objectBoundingBox par defaut), donc un
   meme halo s'adapte au rayon de chaque cercle : une grosse etoile a un halo
   proportionnellement plus large. */
.hero-sky circle,
.hero-sky path.star-spark {
    transform-box: fill-box;      /* scale autour de l'astre, pas du viewBox */
    transform-origin: center;
    /* --o : luminosite de repos de l'astre. --t : periode, --d : phase.
       Les deux sont poses par les nth-child plus bas : sans cela toutes les
       etoiles scintilleraient ensemble, ce qui se voit immediatement. */
    opacity: var(--o, .7);
    animation: star-twinkle var(--t, 4s) ease-in-out infinite;
    animation-delay: var(--d, 0s);
}

@keyframes star-twinkle {
    0%, 100% { opacity: var(--o, .7);   transform: scale(1); }
    50%      { opacity: var(--omax, 1); transform: scale(1.4); }
}

/* Les degrade sont en CSS, pas en attribut de presentation : un stop-color
   en attribut n'accepte pas var(), donc les etoiles ne suivraient pas
   l'accent de l'utilisateur. */
.hero-sky #hs-core stop:nth-child(1) { stop-color: #fff; stop-opacity: 1; }
.hero-sky #hs-core stop:nth-child(2) { stop-color: #fff; stop-opacity: .9; }
.hero-sky #hs-core stop:nth-child(3) { stop-color: var(--primary-2); stop-opacity: .42; }
.hero-sky #hs-core stop:nth-child(4) { stop-color: var(--primary); stop-opacity: .14; }
.hero-sky #hs-core stop:nth-child(5) { stop-color: var(--primary); stop-opacity: 0; }

.hero-sky #hs-neb-a stop { stop-color: var(--primary-2); }
.hero-sky #hs-neb-b stop { stop-color: var(--primary); }
.hero-sky #hs-neb-c stop { stop-color: var(--primary-3); }

/* --- Nappe de fond, c'est elle qui donne l'aspect "espace diffus" --------
   Trois taches enormouses et tres transparentes, jamais animees : sans elles
   le ciel est un aplat noir et les etoiles flottent dessus sans rien. */
.hero-sky .neb { opacity: .5; }
.hero-sky .neb-a { opacity: .16; }
.hero-sky .neb-b { opacity: .12; }
.hero-sky .neb-c { opacity: .08;  }

/* --- Profondeurs : trois ranges, trois ambiances ---------------------- */
.hero-sky__far  circle { --o: .38; --omax: .78; }
.hero-sky__mid  circle { --o: .6;  --omax: 1; }
.hero-sky__near circle { --o: .8;  --omax: 1; }
.hero-sky__far  path.star-spark { --o: .55; --omax: 1; }
.hero-sky__near path.star-spark { --o: .75; --omax: 1; }

/* Decalage de phase et periodes, par groupe. Les motifs (5n, 4n, 3n)
   repartissent les astres en 4, 4 et 3 groupes -- douze cadences distinctes
   au total. Des --d negatifs : une etoile est donc deja en cours de cycle au
   chargement, au lieu de toutes de demarrer a opacite maximale ensemble. */
.hero-sky__far circle:nth-child(5n+1) { --d: -0.6s; --t: 4.6s; }
.hero-sky__far circle:nth-child(5n+2) { --d: -2.1s; --t: 5.9s; }
.hero-sky__far circle:nth-child(5n+3) { --d: -3.4s; --t: 4.1s; }
.hero-sky__far circle:nth-child(5n+4) { --d: -1.3s; --t: 6.4s; }
.hero-sky__far circle:nth-child(5n+5) { --d: -4.4s; --t: 5.2s; }

.hero-sky__mid circle:nth-child(4n+1) { --d: -0.9s; --t: 3.7s; }
.hero-sky__mid circle:nth-child(4n+2) { --d: -2.6s; --t: 4.8s; }
.hero-sky__mid circle:nth-child(4n+3) { --d: -1.8s; --t: 3.1s; }
.hero-sky__mid circle:nth-child(4n+4) { --d: -3.9s; --t: 5.3s; }

.hero-sky__near circle:nth-child(3n+1) { --d: -0.4s; --t: 2.9s; }
.hero-sky__near circle:nth-child(3n+2) { --d: -1.6s; --t: 3.5s; }
.hero-sky__near circle:nth-child(3n+3) { --d: -2.4s; --t: 2.3s; }

.hero-sky__near path.star-spark     { --d: -1.1s; --t: 3.8s; }
.hero-sky__far  path.star-spark     { --d: -2.8s; --t: 5.5s; }

/* Un ciel ou chaque etoile pulse est precisely ce que prefers-reduced-motion
   demande d'eviter. Le decor reste en place, fige : on ne retire pas
   l'information, seulement le mouvement. */
@media (prefers-reduced-motion: reduce) {
    .hero-sky circle,
    .hero-sky path.star-spark { animation: none; opacity: calc(var(--o, .7) + .15); }
}

/* --- Etoiles filantes ---------------------------------------------------
   Conteneur en <div>, pas dans le SVG : une meteore se remplit de ses propres
   couleurs (--m-col, tire par meteors.js) et se deplace par un transform anime
   en JS. Un <span> en position absolue evite d'avoir a dessiner la trainee en
   SVG, et le degrade fait le reste.

   meme plan que le ciel (z-index 0), .hero-content au-dessus : la meteore
   passe donc bien derriere le globe et le titre. */
/* meme plan que le ciel (z-index 0), .hero-content au-dessus : la meteore
   passe donc bien derriere le globe et le titre.

   Meme masque que .hero-sky, et pour la meme raison : une trainee qui sort par
   le bas se ferait couper net sur l'arete de .hero-section. Elle disparait
   souvent seule -- son animation se termine en opacity 0 -- mais pas toujours :
   le bas du cadre est precisement la ou elle est la plus visible. */
.hero-meteors {
    position: absolute; inset: 0; z-index: 0;
    overflow: hidden; pointer-events: none;
    -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 42%, transparent 100%);
    mask-image: linear-gradient(180deg, #000 0%, #000 42%, transparent 100%);
}

/* La trainee : transparente a gauche, couleur, puis blanc au point le plus
   lumineux -- c'est la tete. border-radius sur toute la hauteur evite que la
   trainee ne s'arrete net sur une hauteur de 1 a 2px. */
.meteor {
    position: absolute;
    border-radius: 999px;
    transform-origin: 0 50%;   /* rotation a la queue : la tete part devant */
    /* --m-col et --m-col-fade sont poses par meteors.js, teinte par teinte :
       la trainee s'eclaircit vers la tete, qui est blanche. */
    background: linear-gradient(90deg,
        transparent 0%,
        var(--m-col-fade) 32%,
        var(--m-col) 72%,
        #fff 100%);
    /* Deux ombres portees : une courte et dense pour la tete, une large et
       faible pour la lueur. Une seule donnerait un baton lumineux sans
       profondeur. */
    box-shadow:
        0 0 5px var(--m-col),
        0 0 16px var(--m-col-fade);
    will-change: transform, opacity;
}
/* Cascade : chaque bloc du hero apparait decale. 'both' sur fill-mode garde
   l'element invisible pendant son delai, sans quoi tout s'affiche d'un coup.
   L'animation n'est plus portee par .hero-content, sinon le parent et les
   enfants se fonduaient deux fois. */
.hero-content > * { animation: fadeIn .8s ease-out both; }
.hero-content > :nth-child(1) { animation-delay: 0.05s; }
.hero-content > :nth-child(2) { animation-delay: 0.18s; }
.hero-content > :nth-child(3) { animation-delay: 0.31s; }
.hero-content > :nth-child(4) { animation-delay: 0.44s; }
@media (prefers-reduced-motion: reduce) {
    .hero-content > * { animation: none; }
}
.hero-logo { font-size: clamp(2.5rem, 12vw, 4rem); margin-bottom: 10px; filter: drop-shadow(0 0 20px var(--primary)); }
.hero-title { font-size: clamp(1.9rem, 7.5vw, 4rem); font-weight: 900; margin-bottom: 20px; line-height: 1.15; background: linear-gradient(to right, var(--primary-2), var(--primary), var(--primary-3)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.hero-subtitle { font-size: clamp(1rem, 3.6vw, 1.4rem); color: var(--text-dim); margin-bottom: clamp(25px, 7vw, 50px); max-width: 600px; margin-left: auto; margin-right: auto; }

/* Connexion en modale. <dialog> natif : display:none tant que ferme (feuille
   UA), puis calque superieur + fond flou ::backdrop quand showModal() est
   appele. Pas de dependance, contrairement aux modales SweetAlert2 du reste
   du site, car l'accueil ne charge pas la bibliotheque. */
.login-modal {
    width: min(100%, 486px);
    padding: 46px;
    box-sizing: border-box;
    border: 0;
    background: transparent;
    color: var(--text);
    margin: auto;
}

/* Sans JS la classe .js est absente du <html> : showModal() n'existe pas et la
   boite resterait fermee. Le login etant la seule entree du site, on l'affiche
   alors en clair, dans le flux normal de la page. */
html:not(.js) .login-modal { display: block; margin: clamp(35px, 8vw, 50px) auto; }

.login-modal::backdrop { background: rgba(0,0,0,0.55); backdrop-filter: blur(4px); }
.login-modal[open] { animation: loginModalIn .28s cubic-bezier(.22,1,.36,1); }
@keyframes loginModalIn {
    from { opacity: 0; transform: translateY(16px) scale(.97); }
    to   { opacity: 1; transform: none; }
}
.login-close {
    position: absolute; top: 10px; right: 10px;
    width: 36px; height: 36px;                       /* cible tactile */
    display: flex; align-items: center; justify-content: center;
    border: 1px solid var(--white-10); border-radius: 10px;
    background: var(--white-05); color: var(--text-dim);
    cursor: pointer; transition: background-color .2s, color .2s;
}
.login-close:hover { background: var(--white-10); color: var(--text); }
.login-close:focus-visible { outline: 2px solid var(--primary-2); outline-offset: 2px; }

.login-card {
    position: relative;                             /* ancre du bouton fermer */
    background: #00000069;
    padding: clamp(22px, 6vw, 40px);
    border-radius: clamp(18px, 4vw, 25px);
    border: 1px solid var(--white-10);
    box-shadow: 0 20px 50px var(--black-50);
    width: 100%;
    max-width: 470px;
    backdrop-filter: blur(10px);
}

/* width:100% : la section est un flex-item de .hero-container (align-items:center),
   sans largeur explicite son shrink-to-fit pouvait depasser la fenetre. */
.features-section { padding: clamp(30px, 7vw, 70px) clamp(16px, 5vw, 50px); max-width: 1200px; margin: auto; width: 100%; display: grid; grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr)); gap: clamp(12px, 3vw, 20px); box-sizing: border-box; }
/* Ligne compacte : icone d'un cote, titre + texte de l'autre, en alternance.
   min-width:0 sur l'item et sur le corps : sans ca la grille (min-width:auto)
   refuse de descendre sous la largeur du texte et deborde a droite. */
.feature-item { display: flex; align-items: center; gap: clamp(12px, 4vw, 18px); min-width: 0; background: var(--white-03); padding: clamp(14px, 4vw, 22px); border-radius: clamp(14px, 3vw, 18px); border: 1px solid var(--white-05); text-align: left; transition: transform 0.3s, border-color 0.3s; }
.feature-item:hover { transform: translateY(-4px); border-color: var(--primary); }
/* icone Lucide : le SVG ignore font-size, on le dimensionne nous-memes */
.feature-icon { display: block; flex-shrink: 0; order: 0; color: var(--primary); }
.feature-icon svg { width: clamp(1.9rem, 6.5vw, 2.6rem); height: auto; aspect-ratio: 1 / 1; }
.feature-body { min-width: 0; order: 1; }
/* Alternance portee par une classe et non :nth-child(even) : l'en-tete de
   section ajoute un enfant et aurait inverse l'ordre des items. */
.feature-item--flip .feature-icon { order: 2; }
.feature-item--flip .feature-body { order: 1; }
.feature-item h4 { font-size: clamp(1.05rem, 3.4vw, 1.25rem); margin: 0 0 4px; color: #fff; overflow-wrap: anywhere; }
.feature-item p { color: var(--text-dim); font-size: 0.9rem; margin: 0; overflow-wrap: anywhere; }

/* En-tete de section : grid-column 1/-1 le fait passer au-dessus de la grille,
   les items repartant sur leurs propres lignes. */
.features-head { grid-column: 1 / -1; text-align: center; margin-bottom: clamp(6px, 2vw, 18px); }
.features-title { font-size: clamp(1.5rem, 5vw, 2.4rem); font-weight: 900; margin: 0 0 8px; letter-spacing: 0.3px; background: linear-gradient(to right, var(--primary-2), var(--primary), var(--primary-3)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; overflow-wrap: anywhere; }
.features-subtitle { color: var(--text-dim); font-size: clamp(0.95rem, 3.2vw, 1.1rem); margin: 0; }

.landing-footer { width: 100%; padding: clamp(32px, 7vw, 54px) clamp(16px, 5vw, 50px) clamp(20px, 4vw, 28px); border-top: 1px solid var(--white-05); color: var(--text-dim); font-size: 0.9rem; }
/* Plus de bordure interne : .footer-bottom est le seul bloc, donc un
   border-top y dessinerait un second trait a quelques pixels de celui du
   footer lui-meme. */
.footer-bottom { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px 24px; font-size: 0.8rem; color: var(--text-dim); opacity: 0.8; }
.login-card h3 { margin-bottom: 25px; font-weight: 300; letter-spacing: 2px; }
.login-btn { 
    width: 100%; padding: 15px; border-radius: 12px; border: none; background: linear-gradient(to right, var(--primary), var(--primary-2));
    color: white; font-weight: bold; font-size: 1rem; cursor: pointer; transition: all 0.3s; margin-top: 10px;
}
.login-btn:hover { transform: translateY(-2px); box-shadow: 0 5px 15px var(--primary-40); filter: brightness(1.1); }

/* --- Messages du formulaire : erreur et avertissement ---

   Meme grammaire visuelle que les toasts de base.css : filet de couleur a
   gauche, icone, texte sur un fond de carte. Un texte seul en --danger se lisait
   comme une phrase posee a plat sur le fond de la modale -- sans cadre, donc sans
   dire ou le probleme commence ni ou il finit. Les deux messages partagent la meme
   base et ne different que par la couleur : un deploiement inacheve ne doit pas
   s'afficher comme une faute de l'utilisateur.

   Le message est absent tant qu'il n'a rien a dire. Le serveur rend l'element
   TOUJOURS, vide et hidden, pour que le script trouve ou ecrire sans avoir a le
   creer -- et pour qu'un echec laisse la boite propre plutot qu'un cadre vide. */
.login-error,
.login-warning {
    display: flex; align-items: flex-start; gap: 10px;
    margin: 0 0 16px; padding: 11px 14px;
    background: var(--panel-glass);
    border: 1px solid var(--white-10); border-left: 4px solid var(--danger);
    border-radius: 12px;
    color: var(--text); font-size: 0.85rem; line-height: 1.45;
}
.login-error[hidden] { display: none; }
.login-error-icon { flex: none; margin-top: 1px; color: var(--danger); }

.login-warning { border-left-color: var(--warning); } /* voir .login-error */
.login-warning-icon { flex: none; margin-top: 1px; color: var(--warning); }
.login-btn--cta { text-decoration: none; display: inline-block; width: auto; padding: 15px 40px; }

/* Recuperation impossible. Deux morceaux : l'accroche d'abord, l'explication
   ensuite, pour que l'oeil les grise d'un coup. */
.login-note {
    color: var(--text-dim); font-size: 0.78rem; line-height: 1.5;
    margin: 18px 0 0; padding-top: 16px; border-top: 1px solid var(--white-06);
    opacity: 0.9;
}
.login-note-label { display: block; color: var(--text); font-weight: 600; margin-bottom: 3px; }

/* Renvoi vers l'inscription. Chaine de liens du meme niveau que celle de la
   modale elle-meme. */
.login-alt { color: var(--text-dim); font-size: 0.85rem; margin: 14px 0 0; text-align: center; }
.login-alt a { color: var(--primary-2); text-decoration: none; font-weight: 600; }
.login-alt a:hover { text-decoration: underline; }

/* --- Jauge de robustesse (inscription, reglages, admin) ---

   Le composant est partage par trois pages qui n'ont pas les memes feuilles
   de style entre les mains : il vit donc ici, et admin.css /
   settings.css chargent landing.css ou redefinissent le strict necessaire.
   La couleur est portee par la classe d'etat, jamais en ligne -- seule la
   largeur de la barre est calculee en JavaScript, aucune classe ne pouvant
   exprimer "3 sur 4". */
.pw-meter { display: flex; align-items: center; gap: 10px; margin-top: 10px; }
.pw-meter-track {
    flex: 1; height: 5px; border-radius: 3px;
    background: var(--white-10); overflow: hidden;
}
.pw-meter-bar {
    height: 100%; width: 0; border-radius: 3px;
    background: var(--white-20);
    transition: width .25s ease, background-color .25s ease;
}
.pw-meter-label {
    font-size: 0.7rem; font-weight: 700; letter-spacing: .3px;
    color: var(--text-dim); min-width: 4.5em; text-align: right;
}
.pw-meter.is-empty .pw-meter-track { opacity: .5; }
.pw-meter.is-weak   .pw-meter-bar { background: var(--danger); }
.pw-meter.is-weak   .pw-meter-label { color: var(--danger); }
.pw-meter.is-fair   .pw-meter-bar { background: var(--warning); }
.pw-meter.is-fair   .pw-meter-label { color: var(--warning); }
.pw-meter.is-strong .pw-meter-bar { background: var(--primary-2); }
.pw-meter.is-strong .pw-meter-label { color: var(--primary-2); }
.pw-meter.is-great  .pw-meter-bar { background: var(--success); }
.pw-meter.is-great  .pw-meter-label { color: var(--success); }

/* --- Page d'inscription : RIEN A ECRIRE ICI.

   Elle rend la meme modale que la connexion, au meme endroit du DOM, avec
   les memes classes : <dialog class="login-modal"> contenant une
   <div class="login-card">, par-dessus le hero. Il n'y a donc pas une seule
   regle propre a cette page dans cette feuille, et c'est le but : une
   inscription qui aurait sa propre carte divergerait de la connexion des le
   premier pixel, alors que les deux sont le meme geste.

   Seuls deux elements sont propre a l'inscription, parce qu'ils n'ont pas
   d'equivalent dans la connexion : le bandeau du tout premier compte, et la
   ligne de rappel sur la longueur du mot de passe. */

.signup-note {
    font-size: 0.78rem; color: var(--warning); line-height: 1.5;
    background: var(--primary-10); border: 1px solid var(--primary-25);
    border-radius: 10px; padding: 9px 12px; margin: 0 0 18px;
}
.signup-hint { color: var(--text-dim); font-size: 0.72rem; margin: 8px 0 0; }

/* Bouton d'action principal (accueil / admin) */
.btn-new { background: linear-gradient(to right, var(--primary), var(--primary-2)); color: white; border: none; padding: 14px 28px; border-radius: 15px; font-weight: bold; cursor: pointer; transition: all 0.3s; box-shadow: 0 4px 15px var(--primary-30); display: inline-flex; align-items: center; justify-content: center; gap: 8px; }
.btn-new:hover { transform: scale(1.05); box-shadow: 0 6px 20px var(--primary-50); }
.btn-new--sm { padding: 10px 20px; font-size: 0.85rem; }

@keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }

/* Utils */

.trip-title-wrapper {transition: all 0.2s;padding: 0;border-radius: 8px;margin-left: -7px;}
.trip-title-wrapper:hover {/* background: var(--white-10); *//* transform: translateY(-1px); */}
.trip-title-wrapper:active { transform: translateY(0); background: var(--white-05); }

/* ---------- Section "A propos" : le recit ----------

   Un recit en cinq chapitres sur un rail vertical, pas une grille de cartes.
   L'ordre compte, donc le rail porte la progression : il se remplit a mesure
   que le visiteur descend, ce qui donne la sensation d'avancer dans l'histoire
   plutot que de juxtapositionner desboites. */
.about-section { padding: clamp(30px, 7vw, 70px) clamp(16px, 5vw, 50px); max-width: 1200px; margin: auto; width: 100%; box-sizing: border-box; }
.about-head { text-align: center; margin-bottom: clamp(24px, 5vw, 44px); }
.about-eyebrow {
    display: inline-block; margin-bottom: 10px;
    font-size: 0.74rem; font-weight: 700; text-transform: uppercase; letter-spacing: 1.6px;
    color: var(--primary);
}

.story { list-style: none; margin: 0; padding: 0; position: relative; }

/* Le rail : un trait vertical a gauche des cartes, et un second trait par
   dessus qui suit la progression du scroll (--story-progress pose par
   reveal.js). Les deux sont des pseudo-elements du <ol> : aucun markup en
   plus, et le rail disparait avec la liste s'il est absent. */
.story::before, .story::after {
    content: ''; position: absolute; left: 15px; top: 10px; bottom: 10px;
    width: 2px; border-radius: 2px; pointer-events: none;
}
.story::before { background: var(--white-08); }
.story::after {
    background: linear-gradient(var(--primary), var(--primary-2));
    transform-origin: top center;
    transform: scaleY(var(--story-progress, 0));
}

.story-step { position: relative; padding-left: 48px; padding-bottom: clamp(18px, 3vw, 30px); }
.story-step:last-child { padding-bottom: 0; }

/* Pastille de numero, a cheval sur le rail. */
.story-num {
    position: absolute; left: 0; top: 0;
    width: 32px; height: 32px;
    display: flex; align-items: center; justify-content: center;
    border-radius: 50%;
    background: var(--darker);
    border: 2px solid var(--white-08);
    color: var(--text-dim);
    font-size: 0.72rem; font-weight: 700; letter-spacing: 0.02em;
    transition: border-color .3s, color .3s, background-color .3s;
}
.story-step.is-revealed .story-num { border-color: var(--primary); color: var(--primary); }

.story-card {
    background: var(--white-03);
    border: 1px solid var(--white-05);
    border-radius: clamp(14px, 3vw, 18px);
    padding: clamp(16px, 3.4vw, 24px);
    transition: transform .3s, border-color .3s, background-color .3s;
}
.story-card:hover { transform: translateY(-3px); border-color: var(--white-10); }

/* Mapify est le produit qu'on vend ici : sa carte se distingue des autres
   chapitres, qui sont des complements. */
.story-card--lead { border-color: var(--primary-30); background: var(--white-05); }

.story-icon { display: block; color: var(--primary); margin-bottom: 10px; }
.story-icon svg { width: 22px; height: 22px; }
.story-title { font-size: clamp(1.05rem, 3.6vw, 1.3rem); font-weight: 800; color: #fff; margin: 0 0 6px; }
.story-text { color: var(--text-dim); font-size: 0.93rem; line-height: 1.7; margin: 0; }

/* Chaque sortie de chapitre est un bouton, pas un simple texte colore. Un lien
   de la meme couleur que le texte voisin, sans fond ni contour, ne se lit pas
   comme cliquable, et le seul retour au survol etait un decalage de 4px. */
.story-link {
    display: inline-flex; align-items: center; justify-content: center; gap: 7px;
    min-height: 36px; padding: 8px 15px; margin-top: 12px;
    border-radius: 10px; background: var(--primary); color: #fff;
    box-shadow: 0 4px 14px var(--primary-30);
    text-decoration: none; font-size: 0.86rem; font-weight: 700;
    transition: filter .2s, transform .2s;
}
.story-link:hover { filter: brightness(1.12); transform: translateY(-1px); }
.story-link:focus-visible { outline: 2px solid var(--primary); outline-offset: 3px; border-radius: 4px; }
.story-link svg { flex-shrink: 0; }

/* Mapify est le produit qu'on vend ici, donc son lien reste le plus apparent
   des quatre : meme fond, un cran au-dessus. */
.story-link--lead {
    padding: 11px 20px; font-size: 0.92rem;
    box-shadow: 0 6px 22px var(--primary-40);
}

/* Les gestes de soutien -- GitHub Sponsors et le don PayPal -- sortent du
   violet, qui est la couleur d'accent de l'application : sur ces boutons-la,
   elle dirait "action principale" alors qu'il ne s'agit que de liens tiers.
   Rose fonce -- assez sombre pour tenir le texte blanc au-dessus de 4.5:1
   (ici 5.9:1). Les declinaisons d'opacite reprennent la convention des
   suffixes de base.css ; elles restent locales, deux usages ne justifient pas
   huit entrees dans :root. Le survol reste celui de la base, un
   brightness(1.12) qui n'a pas besoin de connaitre la couleur.

   Les deux boutons partagent ce rose : c'est le meme geste, il doit avoir le
   meme traitement. Une seule declaration pour les deux, sinon le prochain
   lien de soutien se retrouve avec une troisieme nuance.
   --support-30 sert a la box-shadow et au survol de la carte, qui pose un
   transform: none a prefers-reduced-motion : meme declinaison, meme defaut. */
.story-link--sponsors,
.story-link--donate {
    --support: #c2185b;
    --support-30: rgba(194, 24, 91, 0.3);
    --support-focus: #f06292;
    background: var(--support);
    box-shadow: 0 4px 14px var(--support-30);
}
/* L'anneau de focus de la base est violet, il virerait au violet sur un
   bouton rose. Il faut plus clair que le fond du bouton, sinon il se noie
   dans la carte -- d'ou un rose clair, et pas la meme valeur que --support. */
.story-link--sponsors:focus-visible,
.story-link--donate:focus-visible { outline-color: var(--support-focus); }

/* Les deux gestes de soutien sur une ligne. flex-wrap pour qu'un telephone
   les empile proprement plutot que de les faire deborder : 36px de haut, la
   taille tactile du reste du site.

   justify-content: center n'est pas decoratif, il est OBLIGATOIRE. Le bouton
   seul etait un inline-flex, donc centre par le text-align: center herite de
   .hero-container. Un conteneur flex est une boite de niveau bloc : ses items
   sont centres par justify-content, pas par text-align, qui ne s'y applique
   pas. Sans cette ligne, l'ajout du deuxieme bouton a fait passer l'ensemble
   a gauche -- le conteneur est la seule raison du recentrage. */
.story-actions {
    display: flex; flex-wrap: wrap; justify-content: center;
    gap: 10px; margin-top: 12px;
}
.story-actions .story-link { margin-top: 0; flex: 0 1 auto; }

.about-cta { text-align: center; color: var(--text-dim); font-size: 0.9rem; margin: clamp(22px, 4vw, 36px) 0 0; }
/* Lien en milieu de phrase : pas de bouton, il casserait la ligne. Mais le
   soulinement doit etre permanent -- au seul survol, le lien est invisible au
   repos et le visiteur ne sait pas qu'il peut cliquer. */
.about-cta a { color: var(--primary); font-weight: 700; text-underline-offset: 3px; }
.about-cta a:hover { text-decoration-thickness: 2px; }
.about-cta a:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; border-radius: 3px; }

/* ---------- Apparition au scroll ----------

   Le masquage n'est pose que si .js-reveal est sur <html>, donc une page sans
   JS affiche tout le recit immediatement. Le decalage vertical plutot qu'un
   simple fade evite l'effet "tout apparait a plat" d'un simple opacity. */
.js-reveal [data-reveal] { opacity: 0; transform: translateY(26px); }
.js-reveal [data-reveal].is-revealed {
    opacity: 1; transform: none;
    transition: opacity .6s ease, transform .6s cubic-bezier(.22, 1, .36, 1);
}
/* Leger retard interne pour que la carte se pose d'un bloc. */
.js-reveal .story-card > * { opacity: 0; transform: translateY(10px); }
.js-reveal .story-step.is-revealed .story-card > * {
    opacity: 1; transform: none;
    transition: opacity .5s ease, transform .5s ease;
}
.js-reveal .story-step.is-revealed .story-card > :nth-child(1) { transition-delay: .06s; }
.js-reveal .story-step.is-revealed .story-card > :nth-child(2) { transition-delay: .12s; }
.js-reveal .story-step.is-revealed .story-card > :nth-child(3) { transition-delay: .18s; }
.js-reveal .story-step.is-revealed .story-card > :nth-child(4) { transition-delay: .24s; }

@media (max-width: 820px) {
    .story::before, .story::after { left: 13px; }
    .story-step { padding-left: 40px; }
    .story-num { width: 28px; height: 28px; font-size: 0.68rem; }
}

@media (prefers-reduced-motion: reduce) {
    /* On garde la revelation, on retire le mouvement : sans cela le contenu
       resterait a opacity 0 jusqu'au scroll, ce qui est pire qu'une absence
       d'animation. */
    .js-reveal [data-reveal], .js-reveal .story-card > * {
        transform: none !important;
        transition: opacity .2s linear !important;
    }
    .story::after { display: none; }
    .story-card:hover, .story-link:hover { transform: none; }
}

.footer-links { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 18px; }
.footer-links a {
    display: inline-flex; align-items: center; gap: 7px; min-height: 36px;
    color: var(--text-dim); text-decoration: none; font-size: 0.82rem;
    transition: color .2s;
}
.footer-links a:hover { color: var(--primary); }
.footer-links a svg { width: 15px; height: 15px; opacity: .8; }
.footer-links a:hover svg { opacity: 1; }
.footer-tech { opacity: .75; }
@media (max-width: 640px) {
    .footer-bottom { justify-content: center; text-align: center; }
}
