/* Ecran d'authentification Cons'Eaux.
 *
 * Organisation :
 *   1. Polices        2. Jetons        3. Decor (etage 0)
 *   4. Scene          5. Mouvement     6. Carte et formulaire
 *   7. Repli mouvement reduit          8. Petits ecrans
 *
 * REGLE STRUCTURANTE : aucune `transform` ni `filter` sur un ancetre du
 * formulaire. Une transformation cree un bloc conteneur pour les descendants
 * en `position: fixed`, ce qui casse le placement des menus de gestionnaires
 * de mots de passe - et accessoirement celui de toute fenetre qu'on ajouterait
 * plus tard. Tout ce qui bouge est DANS `.decor`, qui est un frere de `main`.
 */

/* 1. POLICES ============================================================= */

/* IBM Plex Sans, sous-ensemble latin, servi en local. Ce sont les MEMES
 * octets que ceux que sert l'interface v2 - le fichier est repris de son
 * build : l'ecran de connexion parle enfin la meme langue typographique que
 * ce qu'il ouvre. Zero requete externe - lier une police distante sur un
 * ecran de connexion serait a la fois une fuite et un point de panne.
 *
 * UN SEUL FICHIER pour toutes les graisses : c'est une police VARIABLE, et
 * l'interface v2 la sert deja ainsi. 40 Ko couvrent 400, 600 et 700, la ou
 * trois fichiers statiques en auraient coute le triple. La plage `100 700`
 * evite au navigateur de SYNTHETISER un gras, ce qui produirait un rendu
 * epaissi et flou different de celui de l'application.
 *
 * `font-display:swap` : le texte s'affiche immediatement dans la police de
 * repli, puis bascule. Sur un ecran de connexion, un champ invisible pendant
 * le chargement d'une police serait un defaut bien pire qu'un changement de
 * fonte. */
@font-face{
  font-family:"Plex Sans"; font-style:normal; font-weight:100 700;
  font-display:swap;
  src:url("/auth/static/fonts/plex-sans-latin.woff2") format("woff2");
}

/* 2. JETONS ============================================================== */

:root{
  --sans:"Plex Sans","Segoe UI",Roboto,-apple-system,BlinkMacSystemFont,
         "Helvetica Neue",Arial,sans-serif;
  --mono:"Cascadia Code","SF Mono",ui-monospace,Consolas,monospace;
  --r:10px; --r-sm:7px;
  --ease:cubic-bezier(.22,.61,.36,1);

  /* Valeurs de repli si le serveur n'a rien pose (rendu hors passerelle). */
  --clarte:0; --nuit:1; --chaleur:0; --soleil-x:.5; --soleil-y:1;
  /* Position du pointeur, 0..1, ecrite par scene.js. Au centre par defaut :
     sans JavaScript, la scene reste cadree comme elle a ete composee. */
  --px:.5; --py:.5;
  /* Intensite generale du mouvement. Passe a 0 par la bascule ou par la
     preference systeme : une seule valeur eteint tout ce qui se deplace. */
  --anim:1;
}

/* Palette de la CARTE. Bascule franche entre deux palettes deja mesurees -
 * jamais un melange des deux, qui fabriquerait des couples de contraste que
 * personne n'a verifies. */
html[data-palette="sombre"]{
  --surface:rgba(14,22,34,.86); --surface-2:rgba(11,17,27,.9);
  --border:rgba(120,190,205,.18);
  /* BORDURE DE CHAMP a 0,52, et non a 0,3. Le critere WCAG 1.4.11 exige 3,0
     pour ce qui IDENTIFIE un composant. Mesure faite : la bordure etait a 1,88
     et le fond du champ ne se detachait de la carte que de 1,04 - donc RIEN ne
     designait le champ, ni son contour ni son remplissage. Sur un fond anime,
     c'est la difference entre « je vois ou taper » et « je cherche ». */
  --border-strong:rgba(150,212,228,.52);
  --ink:#e8eef6; --ink-2:#a2b1c4; --muted:#94a5ba;
  --accent:#2dd4bf; --accent-ink:#5eead4; --accent-soft:rgba(45,212,191,.16);
  --btn-pri-bg:#2dd4bf; --btn-pri-ink:#06231f;
  --crit:#f76d75; --crit-soft:rgba(247,109,117,.18);
  --ombre:0 30px 80px -24px rgba(0,0,0,.8),0 2px 6px rgba(0,0,0,.5);
  --pied:rgba(226,238,248,.62);
}
html[data-palette="claire"]{
  --surface:rgba(255,255,255,.9); --surface-2:rgba(246,248,250,.94);
  --border:rgba(13,20,32,.14); --border-strong:rgba(13,20,32,.54);
  --ink:#0d1420; --ink-2:#4a5568; --muted:#5c6675;
  --accent:#0d9488; --accent-ink:#0a6a61; --accent-soft:#d3f3ee;
  --btn-pri-bg:#0a6a61; --btn-pri-ink:#ffffff;
  --crit:#b0121c; --crit-soft:#fde7e8;
  --ombre:0 30px 80px -26px rgba(13,20,32,.45),0 2px 6px rgba(13,20,32,.12);
  --pied:rgba(13,20,32,.62);
}

*{box-sizing:border-box}
html,body{height:100%}
body{
  margin:0; font-family:var(--sans); color:var(--ink);
  background:#05090f;                     /* jamais de blanc avant peinture */
  -webkit-font-smoothing:antialiased;
  display:grid; grid-template-rows:1fr auto;
  overflow:hidden;                         /* la scene est un decor, pas un document */
}

/* 3. DECOR (ETAGE 0) ===================================================== */

/* `isolation: isolate` N'EST PAS DECORATIF : sans lui, le `mix-blend-mode`
 * des rubans d'aurore et des courants remonte jusqu'a la racine et casse le
 * rendu de la CARTE - mesure faite, son logo, son bouton et son fond
 * cessaient de peindre alors que le DOM les donnait tous visibles, opacite 1,
 * aux bonnes coordonnees. Un element qui melange cherche son fond dans le
 * contexte d'empilement le plus proche ; `z-index:0` en cree un pour
 * l'empilement mais ne suffit pas a borner le GROUPE DE MELANGE. Il faut le
 * dire explicitement.
 *
 * Le symptome vaut la peine d'etre retenu : rien n'est errone dans le DOM,
 * rien n'est errone dans les styles calcules, et pourtant la moitie de la
 * carte a disparu. Aucune relecture de code ne trouve ca - seule une capture
 * le montre. */
.decor{
  position:fixed; inset:0; z-index:0; pointer-events:none; overflow:hidden;
  isolation:isolate;
}
.decor > *{position:absolute; inset:0}

/* Ciel. Trois couches empilees, chacune pilotee par un cadran different :
 *
 * LE JOUR EST UN AZUR PROFOND, PAS UN CIEL PALE - et c'est ce qui sauve
 * l'image en journee. La scene tient tout entiere sur un contraste : des
 * traits cyan qui brillent sur un fond sombre. Avec le degrade pale d'origine
 * (#5aa6d8 -> #dfeaf2) le fond devenait plus clair que le trait, la brume
 * delavait les collines, et il ne restait plus rien de l'ecran valide : un
 * visiteur de dix heures du matin voyait une AUTRE image.
 *
 * La correction n'est pas de brider l'heure du jour - un plafond sur `clarte`
 * a ete essaye et rendait un gris-bleu terne, ni nuit ni jour. C'est la
 * COULEUR de la journee qui etait en cause. Le soleil suit toujours sa course,
 * la palette de la carte bascule toujours, les panneaux s'allument toujours -
 * mais le ciel de midi est desormais l'azur franc d'une belle journee vu
 * depuis un tirage argentique, et le trait cyan y garde sa lumiere.
 * la nuit profonde, le bleu du jour, la chaleur de l'horizon. Les melanger en
 * un seul degrade aurait demande de choisir entre « sombre » et « chaud » au
 * moment du lever, ou les deux sont vrais en meme temps. */
.ciel{
  background:
    radial-gradient(120% 62% at calc(var(--soleil-x)*100%) 100%,
      rgba(255,176,72,calc(var(--chaleur)*.5)) 0%,
      rgba(214,96,60,calc(var(--chaleur)*.22)) 26%,
      transparent 62%),
    linear-gradient(180deg,
      color-mix(in oklab,#12456e calc(var(--clarte)*100%),#050a16) 0%,
      color-mix(in oklab,#1d6591 calc(var(--clarte)*100%),#0a1224) 46%,
      color-mix(in oklab,#2f83ab calc(var(--clarte)*100%),#11203a) 74%,
      color-mix(in oklab,#4a9cbe calc(var(--clarte)*100%),#16283d) 100%);
}

/* Etoiles : un semis fixe de petits points, revele par --nuit. Fait en
 * degrades radiaux plutot qu'en elements : quelques centaines de noeuds DOM
 * pour un fond decoratif seraient un cout net. */
.etoiles{
  opacity:calc(var(--nuit)*.9);
  background-image:
    radial-gradient(1.4px 1.4px at 12% 18%,rgba(255,255,255,.9),transparent),
    radial-gradient(1.1px 1.1px at 27% 9%,rgba(210,235,255,.75),transparent),
    radial-gradient(1.6px 1.6px at 41% 24%,rgba(255,255,255,.85),transparent),
    radial-gradient(1px 1px at 56% 12%,rgba(200,230,255,.7),transparent),
    radial-gradient(1.3px 1.3px at 68% 27%,rgba(255,255,255,.8),transparent),
    radial-gradient(1px 1px at 79% 8%,rgba(215,238,255,.65),transparent),
    radial-gradient(1.5px 1.5px at 88% 21%,rgba(255,255,255,.8),transparent),
    radial-gradient(1.1px 1.1px at 6% 31%,rgba(220,240,255,.6),transparent),
    radial-gradient(1.2px 1.2px at 34% 34%,rgba(255,255,255,.55),transparent),
    radial-gradient(1px 1px at 62% 36%,rgba(210,235,255,.5),transparent),
    radial-gradient(1.3px 1.3px at 93% 33%,rgba(255,255,255,.6),transparent),
    radial-gradient(1px 1px at 19% 6%,rgba(255,255,255,.7),transparent);
  /* Parallaxe la plus faible de la scene : le ciel profond ne bouge presque
     pas quand on tourne la tete. Le respecter est ce qui rend la profondeur
     credible plutot que decorative. */
  transform:translate3d(calc((var(--px) - .5) * -10px * var(--anim)),
                        calc((var(--py) - .5) * -6px * var(--anim)),0);
}

/* L'AURORE. Elle n'est plus un repli : elle est LE SUJET du ciel, et elle est
 * en CSS pur - donc TOUJOURS la. Le shader WebGL vient enrichir par-dessus,
 * il ne la remplace plus. La distinction compte : le canevas est cree avec
 * `failIfMajorPerformanceCaveat: true`, donc refuse sous GL logiciel, donc
 * absent sur une machine sans carte graphique, en machine virtuelle, en
 * capture automatisee, et chez une part reelle des visiteurs. Faire porter le
 * sujet du ciel par une couche facultative etait l'erreur de fond.
 *
 * TROIS RUBANS, ET NON UN VOILE. Une aurore n'est pas une tache de couleur :
 * c'est un rideau plisse, strie de rais quasi verticaux qui suivent les lignes
 * du champ magnetique. C'est la STRIURE qui la fait reconnaitre - un degrade
 * radial floute dit « lueur verte », jamais « aurore ». Chaque ruban est donc
 * une trame de rais, decoupee par un masque en ellipse et adoucie au flou : la
 * trame donne la matiere, le masque donne la forme, le flou donne l'air.
 *
 * DEUX TRAMES PAR RUBAN, DE PERIODES INCOMMENSURABLES (9/28 et 17/41). Une
 * seule trame se lit comme une rayure de tissu ; deux qui ne retombent jamais
 * en phase se lisent comme une lumiere. Meme raison que la marche de Lissajous
 * du pointeur : c'est l'absence de periode commune qui fait l'organique.
 *
 * PAS DE `mix-blend-mode`, ET C'EST UNE DECISION MESUREE. `screen` etait le
 * choix evident - une aurore emet de la lumiere, elle ne la masque pas - et il
 * a ete essaye. Rendu cote a cote sur les deux rasteriseurs de Chromium, il
 * donne DEUX IMAGES DIFFERENTES : aurore discrete sur le chemin GPU, aurore
 * envahissante qui noie toute la scene sur le chemin logiciel. Le melange n'y
 * est pas fait dans le meme espace de couleur. Un fond decoratif dont
 * l'intensite depend de la carte graphique du visiteur n'est pas un fond
 * decoratif, c'est une loterie.
 *
 * Sur un ciel presque noir, la composition alpha ordinaire donne d'ailleurs
 * presque le meme resultat qu'un `screen` : il suffit de monter l'alpha des
 * rais. On garde le rendu, on perd la dependance au materiel.
 *
 * Le blend a aussi coute une demi-heure de fausse piste : sans lui, la capture
 * headless composite correctement ; avec lui, `page.screenshot()` sans `clip`
 * rend une image FAUSSE - logo tronque, bouton absent - alors que le DOM et
 * les styles calcules sont parfaits et que le pixel du bouton, mesure, vaut
 * exactement le teal attendu. Toute capture de cet ecran passe desormais par
 * un `clip` explicite. */
/* L'aurore OCCUPE le ciel, elle ne le REMPLIT pas : au-dessus d'elle il
 * doit rester du noir, sinon elle cesse d'etre un objet dans le ciel pour
 * devenir la couleur du ciel. */
.aurore-css{opacity:calc(.1 + var(--nuit)*.86)}
.aurore-css .ruban{
  position:absolute; left:-8%; top:-14%; width:116%; height:auto;
  will-change:transform;
}
/* Deux plans, deux amplitudes de parallaxe et deux derives : la planche
 * lointaine bouge moins et plus lentement. Les periodes n'ont pas de diviseur
 * commun, donc le ciel ne se repete jamais a l'oeil. */
.aurore-css .r1{
  opacity:.9;
  animation:derive-1 47s var(--ease) infinite alternate;
}
.aurore-css .r2{
  opacity:.5;
  animation:derive-2 71s var(--ease) infinite alternate;
}
@keyframes derive-1{
  from{transform:translate3d(calc((var(--px) - .5) * -30px * var(--anim)),
                            calc((var(--py) - .5) * -16px * var(--anim)),0)
                 scale(1.02)}
  to  {transform:translate3d(calc((var(--px) - .5) * -30px * var(--anim) + 26px),
                            calc((var(--py) - .5) * -16px * var(--anim) - 10px),0)
                 scale(1.09)}
}
@keyframes derive-2{
  from{transform:translate3d(calc((var(--px) - .5) * -14px * var(--anim)),
                            calc((var(--py) - .5) * -8px * var(--anim)),0)
                 scale(1.06)}
  to  {transform:translate3d(calc((var(--px) - .5) * -14px * var(--anim) - 22px),
                            calc((var(--py) - .5) * -8px * var(--anim) + 8px),0)
                 scale(1)}
}

/* Canevas du shader. Invisible tant que la premiere image n'a pas ete rendue :
 * un canevas noir affiche pendant la compilation serait un defaut visible, la
 * ou l'etage 0 est deja fini. */
.ciel-gl{opacity:0; transition:opacity .8s var(--ease)}
.ciel-gl.pret{opacity:calc(var(--nuit)*.92)}

/* L'astre. Un disque doux, pose sur l'arc solaire calcule. Il descend sous
 * l'horizon avec le soleil reel, et sa chaleur s'eteint avec lui. */
.astre{
  inset:auto; left:calc(var(--soleil-x)*100%); top:calc(6% + var(--soleil-y)*52%);
  width:min(20vw,220px); aspect-ratio:1; margin:0 0 0 min(-10vw,-110px);
  border-radius:50%;
  background:radial-gradient(circle,
    rgba(255,246,224,calc(.55 + var(--clarte)*.4)) 0%,
    rgba(255,206,130,calc(var(--chaleur)*.55 + var(--clarte)*.2)) 34%,
    transparent 66%);
  opacity:calc(var(--clarte)*.85 + var(--chaleur)*.6);
  filter:blur(6px);
  transform:translate3d(calc((var(--px) - .5) * -16px * var(--anim)),
                        calc((var(--py) - .5) * -8px * var(--anim)),0);
}

/* Brume d'horizon : elle detache la bande lointaine du sol et adoucit le
 * raccord entre le ciel et la scene. Volontairement discrete de jour - une
 * brume trop dense delave la scene entiere et lui fait perdre son trait. */
.brume{
  background:linear-gradient(180deg,transparent 27%,
    color-mix(in oklab,rgba(126,176,200,.32) calc(var(--clarte)*100%),
              rgba(18,32,52,.5)) 38%,transparent 54%);
}

/* La scene. Parallaxe la plus forte de tout le decor : c'est l'objet le plus
 * proche du regard. */
.scene-hote{
  transform:translate3d(calc((var(--px) - .5) * -44px * var(--anim)),
                        calc((var(--py) - .5) * -18px * var(--anim)),0);
}
svg.scene{position:absolute; inset:0; width:100%; height:100%}

/* LA PASSE DE BLOOM. Meme geometrie, meme parallaxe que la scene nette -
 * sinon le halo se decolle de son trait des le premier mouvement de souris.
 *
 * UNE SEULE PASSE, ET C'EST UNE MESURE QUI L'A DECIDE. Deux echelles de flou
 * rendaient un halo a peine plus riche pour un tiers de cadence en moins :
 * 6,1 images/s contre 7,4, sur une base de 13,4 sans bloom (rasteriseur
 * LOGICIEL, ou un flou plein ecran est ruineux - la mesure majore donc
 * largement ce que coute un vrai GPU, mais le RAPPORT entre configurations
 * reste valable).
 *
 * Le cout est reel et il est assume : une rasterisation de plus par image.
 * Il est finançable parce que la scene a ete VIDEE (108 Ko contre 246), et il
 * tombe a zero des que le mouvement est reduit. C'est le meilleur echange de
 * toute cette page : rien d'autre ne rapproche autant du modele. */
.scene-bloom{
  transform:translate3d(calc((var(--px) - .5) * -44px * var(--anim)),
                        calc((var(--py) - .5) * -18px * var(--anim)),0);
}
/* LES COPIES FLOUTEES NE S'ANIMENT PAS, ET C'EST CE QUI REND LE BLOOM
 * FINANCABLE. Mesure faite : avec les animations laissees dans les copies, la
 * cadence tombait de moitie - le navigateur doit refaire les deux flous plein
 * ecran a CHAQUE image, puisque leur contenu change. Figees, elles sont
 * rasterisees une fois puis simplement recomposees.
 *
 * Et la perte visuelle est nulle : le flou d'une bulle qui monte est
 * indiscernable du flou de la meme bulle immobile. On ne perd que ce que
 * personne ne pouvait voir.
 *
 * `will-change:filter` a ete RETIRE pour la meme raison : il demande au
 * navigateur de se tenir pret a refiltrer, c'est-a-dire exactement de ne pas
 * mettre en cache ce qu'on veut mettre en cache. */
.scene-bloom *{
  animation:none!important;
  transition:none!important;
}

.scene-bloom-serre{filter:blur(18px) saturate(1.25); opacity:calc(.4 + var(--nuit)*.5)}
/* Mouvement reduit : on garde le bloom - c'est de la lumiere, pas du
 * mouvement - mais on cesse de le recalculer en le figeant avec la scene. */
html[data-mouvement="reduit"] .scene-bloom{will-change:auto}

/* La lueur du pointeur : LE CURSEUR EST LA SOURCE DE LUMIERE.
 * Un disque pre-rendu qu'on DEPLACE, et non un degrade fullscreen recalcule a
 * chaque image. La difference n'est pas cosmetique : repeindre un degrade
 * plein ecran coute plusieurs millisecondes de processeur par image sur un
 * ecran 4K, deplacer une couche deja composee ne coute rien. */
.lueur{
  inset:auto; left:0; top:0; width:920px; height:920px; margin:-460px 0 0 -460px;
  background:radial-gradient(circle,
    rgba(140,235,255,.16) 0%,rgba(90,200,235,.07) 34%,transparent 62%);
  opacity:calc(var(--anim)*(.35 + var(--nuit)*.65));
  will-change:transform;
  transform:translate3d(calc(var(--px)*100vw),calc(var(--py)*100vh),0);
}

/* VOILE DU BAS. Il resout deux problemes d'un seul geste :
 *   1. La scene ne s'arrete plus par une COUPE FRANCHE au bord de l'image -
 *      elle s'estompe, comme le ferait un premier plan hors mise au point. Un
 *      site coupe net au ras du cadre a l'air d'une capture mal recadree.
 *   2. Le pied de page redevient LISIBLE. Sans lui, « Accès réservé » se lit
 *      par-dessus les bassins - illisible de nuit, pire encore de jour.
 * Traite le probleme a la source plutot que d'ajouter une ombre portee au
 * texte, qui n'aurait rien regle du premier point. */
.voile-bas{
  background:linear-gradient(180deg,transparent 82%,
    color-mix(in oklab,rgba(198,220,234,.8) calc(var(--clarte)*100%),
              rgba(5,10,18,.9)) 100%);
}

/* LE FLUX. Des courants lumineux qui traversent le bas du cadre et convergent
 * vers un point unique. C'est la these du produit dite en une image : de
 * l'energie qui vient de partout et se mesure ICI.
 *
 * En `screen`, comme l'aurore : ce sont des lignes qui EMETTENT. Et sans
 * aucun filtre - la lueur vient du foyer pose dessous, pas d'un `drop-shadow`
 * qui forcerait une re-rasterisation du groupe a chaque image, puisque les
 * courants sont animes. */
.flux{
  opacity:calc(.5 + var(--nuit)*.45);
  transform:translate3d(calc((var(--px) - .5) * -18px * var(--anim)),
                        calc((var(--py) - .5) * -8px * var(--anim)),0);
}
.flux svg{position:absolute; inset:0; width:100%; height:100%}
.flux path{
  fill:none; stroke-linecap:round;
  stroke:color-mix(in oklab,#0d6f8e calc(var(--clarte)*100%),#8DF0FF);
}
.flux .courants-lit path{stroke-width:1.1; opacity:.3}
.flux .courants path{
  stroke-width:2.6; opacity:.9;
  /* UN SEUL long tiret par courbe, qui la parcourt. La longueur du motif
     (2600) depasse celle du chemin : a tout instant une seule impulsion est
     visible, ce qui se lit comme un passage et non comme un pointille. */
  stroke-dasharray:460 2140;
  animation:courir 26s linear infinite;
}
.flux .courants path:nth-child(2){animation-duration:31s; animation-delay:-6s}
.flux .courants path:nth-child(3){animation-duration:37s; animation-delay:-14s}
.flux .courants path:nth-child(4){animation-duration:29s; animation-delay:-21s}
.flux .courants path:nth-child(5){animation-duration:41s; animation-delay:-9s}
@keyframes courir{from{stroke-dashoffset:2600} to{stroke-dashoffset:-2600}}

/* LE FOYER : le point de convergence. Un disque doux, pose au bas du cadre.
 * Il respire tres lentement - une pulsation rapide serait a la fois vulgaire
 * et un risque au regard du critere 2.3.1 sur le clignotement. */
.foyer{
  /* POSE AU POINT DE CONVERGENCE, et non « en bas au milieu ». Les courants
     se rejoignent a (800, 868) dans le repere du flux ; le foyer etait deux
     cents pixels plus haut, si bien que la lueur et le noeud ne coincidaient
     pas - on voyait une tache d'un cote et un croisement de l'autre. */
  inset:auto; left:50%; bottom:-19%;
  width:min(34vw,420px); aspect-ratio:1; margin:0 0 0 min(-17vw,-210px);
  border-radius:50%;
  background:radial-gradient(circle,
    rgba(214,250,255,.85) 0%,rgba(96,214,238,.42) 16%,
    rgba(58,158,204,.16) 38%,transparent 66%);
  opacity:calc(.3 + var(--nuit)*.5);
  filter:blur(5px);
  animation:respirer 11s var(--ease) infinite alternate;
}
@keyframes respirer{
  from{transform:scale(.94)} to{transform:scale(1.06)}
}

/* LES TROIS CALQUES PEINTS.
 *
 * `object-fit: cover` et un cadrage par le BAS : le contenu utile de
 * l'illustration - la station, les panneaux, la ligne d'eau - est dans son
 * tiers inferieur. Cadrer par le centre, comme le fait le defaut, couperait
 * le sol sur un ecran moins haut que 16:9 et laisserait du ciel vide en haut.
 *
 * Les calques sont AGRANDIS de 6 % et deplaces : la parallaxe doit avoir de la
 * matiere a decouvrir sur les bords, sinon elle revele le vide au premier
 * mouvement de souris. C'est le meme principe que le debord des rubans
 * d'aurore.
 *
 * TROIS AMPLITUDES, croissantes avec la proximite. Le ciel bouge a peine - on
 * ne voit pas bouger l'horizon quand on tourne la tete - et le premier plan
 * beaucoup. Inverser ce rapport, ou l'egaliser, suffit a detruire la
 * profondeur que le decoupage en calques venait d'acheter. */
.calque{
  position:absolute; inset:-3%; width:106%; height:106%;
  object-fit:cover; object-position:50% 100%;
  will-change:transform;
}
.calque-ciel{
  transform:translate3d(calc((var(--px) - .5) * -4px * var(--anim)),
                        calc((var(--py) - .5) * -3px * var(--anim)),0);
}
.calque-lointain{
  transform:translate3d(calc((var(--px) - .5) * -10px * var(--anim)),
                        calc((var(--py) - .5) * -6px * var(--anim)),0);
}
.calque-proche{
  transform:translate3d(calc((var(--px) - .5) * -20px * var(--anim)),
                        calc((var(--py) - .5) * -11px * var(--anim)),0);
}

.grain{opacity:.05; mix-blend-mode:overlay; width:100%; height:100%}

/* 4. SCENE =============================================================== */

/* Quatre niveaux de trait, stricts. Le rapport entre deux niveaux voisins est
 * d'environ 1,6 : plus resserre, la hierarchie passe pour un accident ; plus
 * ecarte, elle passe pour un dessin animé. */
/* LE RAPPORT ETAIT INVERSE, et c'est la cause chiffree du « ca ne brille
 * pas ». Comptage fait sur le SVG genere de l'etat precedent : 15 elements au
 * niveau silhouette contre 571 au niveau texture (0,5 px, opacite 0,4,
 * pointille) et 128 au niveau intermediaire. Autrement dit 96 % de l'encre de
 * l'image etait de la texture a 40 % d'opacite. Un dessin bati comme ca ne
 * peut produire qu'un voile gris - aucune couleur, aucun halo, aucune palette
 * ne le rattrape.
 *
 * La scene a d'abord ete VIDEE, ce qui a supprime l'essentiel de cette
 * texture. Les niveaux sont maintenant remontes : ce qui reste doit porter. */
.scene *{fill:none; stroke-linecap:round; stroke-linejoin:round}
.scene .t1{stroke:var(--trait-1); stroke-width:2.6}
.scene .t2{stroke:var(--trait-2); stroke-width:1.7; opacity:.94}
.scene .t3{stroke:var(--trait-3); stroke-width:1.05; opacity:.8}
.scene .t4{stroke:var(--trait-3); stroke-width:.7; opacity:.5; stroke-dasharray:4 3}

:root{
  /* Le trait passe du cyan lumineux (nuit) au bleu profond (jour) : sur un
     ciel clair, un trait clair disparaitrait. */
  --trait-1:color-mix(in oklab,#0f4c63 calc(var(--clarte)*100%),#7FE3E8);
  --trait-2:color-mix(in oklab,#186079 calc(var(--clarte)*100%),#5FC6D8);
  --trait-3:color-mix(in oklab,#2a7290 calc(var(--clarte)*100%),#49A8C6);
}

/* Perspective atmospherique : a distance, le trait perd du contraste avec le
 * fond, se refroidit et s'affine. Sur fond sombre cela s'inverse par rapport
 * au dessin sur papier - c'est l'ECART au fond qui diminue, pas la clarte. */
.bande-lointaine{opacity:.66}
.bande-lointaine .t1{stroke-width:1.2; stroke:var(--trait-3)}
.bande-lointaine .t2{stroke-width:.9; stroke:var(--trait-3); opacity:.78}
.bande-moyenne{opacity:.88}
.bande-moyenne .t1{stroke-width:1.8; stroke:var(--trait-2)}
.bande-moyenne .t2{stroke-width:1.25; stroke:var(--trait-2); opacity:.88}
.bande-moyenne .t3{stroke-width:.85; opacity:.66}
.bande-moyenne .t4{stroke-width:.6; opacity:.38}

/* L'eau prend la couleur de ce qu'il y a AU-DESSUS d'elle : la nuit elle
 * capte le vert de l'aurore, le jour le bleu du ciel. Un plan d'eau qui garde
 * la meme teinte a toute heure est le detail qui fait « dessin » plutot que
 * « lieu ». */
.scene .eau,.scene .eau-claire{
  fill:color-mix(in oklab,rgba(120,190,225,.34) calc(var(--clarte)*100%),
                 rgba(38,150,150,.32));
  stroke:var(--trait-2); stroke-width:.6;
}
.scene .eau-chenal{fill:rgba(200,240,255,.14); stroke:none}
.scene .ecume-fine{fill:rgba(150,175,190,.2); stroke:none}
/* La riviere. Tres peu marquee : elle SITUE la station - une station est
 * toujours au bord de son exutoire - sans jamais la concurrencer. Trop
 * appuyee, la bande se lit comme une route ou une rampe. */
.scene .riviere{
  fill:color-mix(in oklab,rgba(120,175,215,.3) calc(var(--clarte)*100%),
                 rgba(35,95,130,.16));
  stroke:none;
}
.scene .rive{stroke:var(--trait-2); opacity:.5}
.scene .courant{stroke:var(--trait-2); opacity:.26; stroke-dasharray:14 9}
.scene .voirie{stroke:var(--trait-3); stroke-width:.7; opacity:.45}
.scene .toit{opacity:.55}
/* La crete se DETACHE d'autant plus que la nuit tombe : c'est l'arete qui
 * porte la derniere lueur du ciel, et c'est elle qui sauve la silhouette. */
.scene .crete{
  stroke:var(--trait-3); stroke-width:1.1;
  opacity:calc(.44 + var(--nuit) * .3);
}
.scene .crete-loin{opacity:.3}
.scene .pale{
  fill:color-mix(in oklab,rgba(30,90,120,.5) calc(var(--clarte)*100%),
                 rgba(120,220,235,.3));
  stroke:var(--trait-2); stroke-width:.9;
}

/* LE CHAMP SOLAIRE. Ses tables sont instanciees par `<use>`, ou aucun
 * selecteur de classe n'entre : tout ce qui les habille doit etre une
 * propriete HERITEE, posee ici sur le groupe qui les contient.
 *   - `color`        -> le trait, via `currentColor` dans le symbole ;
 *   - `fill-opacity` -> le verre, seul element rempli de la table.
 * C'est ce detour qui permet aux panneaux de S'ALLUMER AVEC LE JOUR sans que
 * le fichier de scene soit regenere - un champ solaire la nuit est un champ
 * ETEINT, et le montrer vif a 3 h du matin serait le seul mensonge de toute
 * la scene. */
.scene .champ-pv{
  color:var(--trait-2);
  fill-opacity:calc(.1 + var(--clarte) * .34);
}

/* Le seul blanc de la scene : l'eau qui bout dans le bassin d'aeration. */
.scene .ecume{fill:rgba(216,244,252,.2); stroke:none}

/* Le seul point chaud : la torchere du biogaz. Meme ambre que la goutte du
 * logo Cons'Eaux - un accent employe DEUX fois dans toute la page, jamais trois. */
.scene .feu{fill:#FFB840; stroke:none}
.scene .feu-coeur{fill:#FFE6B0}
/* Les fenetres du batiment d'exploitation, allumees la nuit seulement. */
.scene .fenetre{fill:#FFD98A; stroke:none; opacity:calc(.25 + var(--nuit)*.6)}


/* --- Halo des silhouettes ------------------------------------------------
 * Le trait de niveau 1 du premier plan est redessine dessous, large et tres
 * transparent : le trace devient LUMINEUX au lieu d'etre plat. Fait par
 * duplication de geometrie et non par `drop-shadow` - un filtre sur ce groupe
 * forcerait une re-rasterisation a chaque image, puisque des bulles et des
 * ponts y bougent. Ici, le composeur ne fait plus rien apres la premiere
 * peinture. */
/* Assez pour que le trace paraisse lumineux, pas assez pour qu'on le
 * remarque. Un halo qui se voit n'est plus un halo, c'est du neon - et le
 * dessin cesse d'etre un plan technique. */
/* LE HALO EST LA SEULE LUEUR DE LA SCENE, et il etait regle pour ne pas se
 * voir : 0,10 le jour, 0,26 la nuit. C'etait prudent quand il dupliquait 33 Ko
 * de premier plan ; maintenant qu'il n'extrait plus que les quinze traits de
 * silhouette, il peut - et il doit - porter la lumiere. */
.scene .halos{opacity:calc(.2 + var(--nuit) * .5)}
.scene .halo{
  stroke:var(--trait-1); stroke-width:7; fill:none;
  stroke-linecap:round; stroke-linejoin:round;
  filter:blur(3px);
}

/* --- Le courant d'energie ------------------------------------------------
 * La ligne est permanente et discrete ; l'impulsion qui la remonte est un
 * TIRET qui se deplace, pas un objet qui suit un chemin. Une seule propriete
 * animee, composee par le navigateur sans repasser par la mise en page.
 *
 * `--long` est la longueur reelle du trace, calculee a la generation : le
 * motif fait donc exactement un tour, sans boucle prematuree ni saut. */
.scene .cable{
  stroke:var(--trait-3); stroke-width:.8; opacity:.4; fill:none;
  stroke-dasharray:5 4;
}
.scene .courant-elec{
  stroke:var(--accent); stroke-width:2.2; fill:none; stroke-linecap:round;
  stroke-dasharray:calc(var(--long) * .07) calc(var(--long) * .93);
  animation:courant var(--periode,5s) linear infinite;
  animation-delay:calc(var(--retard,0s) * -1);
  filter:drop-shadow(0 0 3px var(--accent));
}
@keyframes courant{
  from{stroke-dashoffset:calc(var(--long) * 1)}
  to{stroke-dashoffset:calc(var(--long) * -0.07)}
}


/* --- Le canal Albert et sa berge ---------------------------------------
 * La station est posee sur le canal : c'est ce qui la SITUE. Une bande large,
 * sombre et immobile en premier plan, qui prend la couleur du ciel comme le
 * ferait une vraie surface d'eau. */
.scene .canal{
  fill:color-mix(in oklab,rgba(74,132,180,.62) calc(var(--clarte)*100%),
                 rgba(16,52,84,.78));
  stroke:none;
}
/* Deux reflets longitudinaux : c'est ce qui distingue une surface d'EAU d'une
 * dalle de beton. Sans eux, la bande du canal se lit comme une route, et la
 * station cesse d'etre au bord de son exutoire. */
.scene .reflet{
  stroke:color-mix(in oklab,rgba(255,255,255,.42) calc(var(--clarte)*100%),
                   rgba(150,225,240,.3));
  stroke-width:1.6; fill:none; stroke-linecap:round;
}
.scene .reflet-fin{stroke-width:.9; opacity:.55}
.scene .houppier{
  fill:color-mix(in oklab,rgba(58,110,70,.55) calc(var(--clarte)*100%),
                 rgba(24,64,72,.5));
  stroke:none;
}
.scene .arbre{opacity:.72}


/* --- Premier plan --------------------------------------------------------
 * Deux objets tres pres, assez pour sortir du cadre : ils donnent au regard un
 * premier echelon, et la station qui les suit prend d'un coup sa vraie
 * dimension. Ils portent donc le trait le plus fort et le halo le plus net -
 * l'inverse exact de la bande lointaine. */
/* Le premier plan doit PESER : c'est lui qui donne l'echelle a tout le reste.
 * Trait plus large, remplissage plus dense, et une ombre portee qui le detache
 * de la station derriere lui. Sans ce contraste, un objet pourtant tres pres
 * se noie dans le mi-plan et l'echelonnement disparait. */
.bande-avant{opacity:1; filter:drop-shadow(0 6px 14px rgba(0,0,0,.45))}
.bande-avant .t1{stroke-width:2.8}
.bande-avant .t2{stroke-width:2}
.bande-avant .t3{stroke-width:1.1; opacity:.85}
.bande-avant .t4{stroke-width:.8; opacity:.5}
.bande-avant .pale{
  fill:color-mix(in oklab,rgba(30,92,126,.6) calc(var(--clarte)*100%),
                 rgba(120,220,238,.34));
  stroke:var(--trait-1); stroke-width:2.2;
}
/* La machine de tete tourne, et lace lentement : deux mouvements de periodes
 * tres differentes sur le meme objet, c'est ce qui le rend vivant plutot
 * qu'anime. Le lacet est de trois degres sur trente secondes - on ne le voit
 * pas bouger, on remarque seulement qu'il a bouge. */
.eol-tete{animation:lacet 34s ease-in-out infinite alternate;
          transform-box:fill-box; transform-origin:50% 100%}
@keyframes lacet{from{transform:skewX(-1.1deg)} to{transform:skewX(1.1deg)}}

/* Le verre des tables de premier plan : plus dense, et il s'allume avec le
 * jour comme celui du champ lointain. */
.champ-pv-gros{
  color:var(--trait-1);
  fill-opacity:calc(.14 + var(--clarte) * .4);
}


/* Le flanc des versants : un aplat tres pale sous la ligne de crete proche.
 * Sans lui, deux polylignes restent deux traits ; avec lui, ce sont des
 * collines, et la vallee entre elles devient un creux plutot qu'un vide. */
/* TROIS PLANS DE RELIEF, chacun un peu plus contraste que le precedent. Ce
 * n'est pas la couleur de chacun qui compte, c'est l'ECART entre eux : l'oeil
 * ne mesure pas l'eloignement dans l'absolu, il le deduit d'une progression.
 * Trois aplats sans aucun trait de separation suffisent - chacun recouvre le
 * precedent.
 *
 * ET LE RAPPORT S'INVERSE AVEC L'HEURE. De jour, une colline lointaine est
 * PLUS CLAIRE que le sol : c'est la brume qui la delave, et plus elle est
 * loin plus elle pale. De nuit, la meme colline est une SILHOUETTE - le ciel
 * garde une lueur, la terre n'en a plus. Peindre le relief nocturne en clair,
 * comme un relief diurne assombri, le dissout dans le ciel : c'est exactement
 * ce qui s'etait produit ici, et le paysage avait disparu apres le coucher.
 * Les deux termes de chaque color-mix sont donc de sens opposes. */
.scene .versant{
  fill:color-mix(in oklab,rgba(120,168,200,.3) calc(var(--clarte)*100%),
                 rgba(3,12,24,.72));
  stroke:none;
}
.scene .versant-median{
  fill:color-mix(in oklab,rgba(146,186,212,.22) calc(var(--clarte)*100%),
                 rgba(5,16,30,.5));
}
.scene .versant-loin{
  fill:color-mix(in oklab,rgba(168,200,222,.15) calc(var(--clarte)*100%),
                 rgba(8,22,38,.3));
}
/* Parcelles agricoles : une laniere sur deux, a peine plus claire que le
 * versant. Le motif doit se DEVINER, pas se compter - c'est un plateau vu de
 * loin, pas un cadastre. */
.scene .parcelle{
  fill:color-mix(in oklab,rgba(206,226,192,.11) calc(var(--clarte)*100%),
                 rgba(0,6,14,.16));
  stroke:none;
}
.scene .haie{
  stroke:color-mix(in oklab,rgba(58,104,70,.5) calc(var(--clarte)*100%),
                   rgba(2,10,20,.62));
  stroke-width:2.4; stroke-linecap:round; fill:none;
}
.scene .crete-median{opacity:.4}

/* LE PLAN DE SOL. Une seule teinte, deux arrets : eteinte a l'horizon, pleine
 * en bas de cadre. C'est ce qui transforme un schema pose sur un degrade de
 * ciel en une scene qui a un sol - et aucune quantite de detail ajoute ne
 * remplace ca. */
.scene{
  --teinte-sol:color-mix(in oklab,#2a6a72 calc(var(--clarte)*100%),#061524);
}
.scene .sol-a{stop-color:var(--teinte-sol); stop-opacity:0}
.scene .sol-m{
  stop-color:var(--teinte-sol);
  stop-opacity:calc(.04 + var(--nuit) * .04);
}
.scene .sol-b{
  stop-color:var(--teinte-sol);
  stop-opacity:calc(.3 + var(--nuit) * .22);
}
/* LA MEUSE. Une eau de nuit n'est pas bleue : elle est NOIRE, et tout ce
 * qu'on y voit est ce que le ciel y met. C'est pour cela que le fond est
 * presque opaque et que le reflet, lui, porte toute la couleur. */
.scene .eau-meuse{
  fill:color-mix(in oklab,#1d4f68 calc(var(--clarte)*100%),#04101d);
  stroke:none;
}
/* Le reflet ne monte pas avec le jour : a midi il n'y a pas d'aurore a
 * refleter, et une eau qui renverrait un ciel absent serait le detail qui
 * trahit tout le reste. */
.scene .reflet-aurore{opacity:calc(var(--nuit) * .78)}
.scene .ride{
  stroke:color-mix(in oklab,#8fc4d8 calc(var(--clarte)*100%),#9FE8F5);
  stroke-width:1.1; opacity:.34; stroke-linecap:round;
}
.scene .rive-meuse{
  stroke:var(--trait-3); stroke-width:1.1; opacity:.5;
}
/* La bande d'eau est LOIN : elle recoit la meme attenuation que les collines,
 * sinon elle passerait devant la station. */
.scene .bande-meuse{opacity:.92}

.scene .verdure .arbre{opacity:.68}
/* Le rideau est LOIN - derriere tout le site. Il doit donc peser moins que
 * les bosquets qui sont dedans, sans quoi il aspire le regard vers le fond. */
.scene .rideau{opacity:.62}

/* LA LIGNE HAUTE TENSION. Discrete : elle occupe le vide, elle ne le remplace
 * pas par un autre sujet. Les conducteurs sont plus fins que le treillis -
 * c'est l'inverse qui trahit le dessin d'amateur, un cable ne pese jamais plus
 * que la structure qui le porte. */
.scene .ligne-ht{opacity:.5}
.scene .conducteur{
  stroke:var(--trait-3); stroke-width:.7; fill:none; opacity:.72;
}

/* Bois. Deux niveaux d'eloignement, deux densites. */
.scene .arbre-fond{opacity:.72}
.scene .arbre-loin{opacity:.42}
.scene .arbre-fond .houppier,.scene .arbre-loin .houppier{
  fill:color-mix(in oklab,rgba(46,92,62,.55) calc(var(--clarte)*100%),
                 rgba(3,14,26,.7));
}

/* Le village : quelques toits et un clocher. Ce qui le fait lire est le
 * GROUPE et son alignement, jamais le detail des maisons. */
.scene .village{opacity:.62}
.scene .toit{
  fill:color-mix(in oklab,rgba(150,120,110,.5) calc(var(--clarte)*100%),
                 rgba(4,14,26,.74));
  stroke:none;
}
/* LES FENETRES DU VILLAGE, et elles ne s'allument que la nuit. C'est le seul
 * endroit de la scene ou quelqu'un habite ; quatre points chauds a l'horizon
 * suffisent a le dire, et ils disent aussi l'heure - de jour ils n'existent
 * pas, parce qu'on ne voit pas une fenetre eclairee en plein soleil. */
.scene .fenetre{
  fill:#f6b352; stroke:none;
  opacity:calc(var(--nuit) * .8);
}

/* 5. MOUVEMENT =========================================================== */

/* DEUX BASES DE TEMPS DECLAREES, et non un jeu de valeurs arbitraire.
 * L'ecart reel entre un pont racleur (45 min par tour) et un rotor d'eolienne
 * (6 s) est de 450 pour 1 : on ne peut pas le conserver et rendre les deux
 * visibles. Alors :
 *   - GROUPE 1x, en temps reel : rotor 6 s, aerateur 1,5 s, vis 3 s. Ces
 *     vitesses sont DEJA les bonnes, on ne les ralentit pas.
 *   - GROUPE 60x : pont racleur 45 s, pont roulant 10 s. Le MEME facteur pour
 *     tout le groupe lent, donc les proportions restent exactes entre eux.
 * Deux groupes internement honnetes valent mieux qu'un jeu de chiffres
 * plausibles mais faux. */
@keyframes tourne{to{transform:rotate(360deg)}}
@keyframes va_et_vient{
  0%,100%{transform:translateX(0)}
  50%{transform:translateX(56px)}
}
/* Une bulle nait au fond, grossit en montant, creve en surface. Le
 * deplacement vertical est MINUSCULE - deux pixels - parce qu'a cette echelle
 * quatre metres d'eau ne font que quelques pixels : ce qui se lit, c'est le
 * grossissement et l'apparition, pas la course. */
@keyframes bulle{
  0%{opacity:0; transform:scale(.35) translateY(1px)}
  22%{opacity:.85}
  70%{opacity:.7}
  100%{opacity:0; transform:scale(1.35) translateY(-2.5px)}
}
@keyframes respire{
  0%,100%{opacity:.5}
  50%{opacity:.78}
}
@keyframes vacille{
  0%,100%{transform:scaleY(1) scaleX(1); opacity:.9}
  35%{transform:scaleY(1.16) scaleX(.92); opacity:1}
  70%{transform:scaleY(.9) scaleX(1.06); opacity:.8}
}
@keyframes defile{to{stroke-dashoffset:-24}}

.scene .pont{
  transform-origin:0 0;
  animation:tourne var(--periode,45s) linear infinite;
  animation-delay:calc(var(--phase,0deg) / 360deg * var(--periode,45s) * -1);
}
.scene .pont-roulant{animation:va_et_vient 10s ease-in-out infinite}
.scene .bulle{
  fill:rgba(226,248,255,.62); stroke:none;
  transform-box:fill-box; transform-origin:center;
  animation:bulle 1.6s ease-out infinite;
  animation-delay:calc(var(--retard,0ms) * -1);
}
/* Le tapis d'ecume respire lentement au-dessus des diffuseurs. */
.scene .ecume{animation:respire 2.6s ease-in-out infinite}
.scene .flamme{
  transform-box:fill-box; transform-origin:bottom center;
  animation:vacille 1.1s ease-in-out infinite;
}
.scene .helice{stroke-dasharray:6 5; animation:defile 3s linear infinite;
               animation-delay:calc(var(--phase,0ms) * -1)}
.scene .helice-arret{animation:none}   /* une vis a l'arret : c'est le cas reel */
.scene .rotor{
  transform-origin:0 0;
  animation:tourne var(--periode,6s) linear infinite;
  animation-delay:calc(var(--phase,0deg) / 360deg * var(--periode,6s) * -1);
}

/* 6. CARTE ET FORMULAIRE ================================================= */

main{
  position:relative; z-index:1;
  display:flex; align-items:center; justify-content:center;
  padding:2rem 1.25rem 1rem; min-height:0;
}
.carte{
  position:relative; width:100%; max-width:408px;
  border-radius:14px; box-shadow:var(--ombre);
  /* Le fond est OPAQUE A 86 % ET FLOUTE. Le flou est un bonus : le contraste
     du texte est verifie contre le fond seul, sans lui. `backdrop-filter`
     coute cher au-dessus d'un canevas anime - il refloute a chaque image - et
     Safari le rend mal sous un ancetre a `overflow:hidden`. S'il n'est pas
     applique, la carte reste parfaitement lisible. */
  background:var(--surface);
  -webkit-backdrop-filter:blur(16px) saturate(1.2);
  backdrop-filter:blur(16px) saturate(1.2);
  border:1px solid var(--border);
  overflow:hidden;
}
.dedans{position:relative; z-index:1; padding:2.1rem 1.85rem 1.6rem}

/* Le rehaut : une lumiere de bord qui suit le pointeur. Aucune transformation
 * n'est appliquee a la carte - seule la POSITION du degrade change, donc le
 * bloc conteneur du formulaire reste intact et les gestionnaires de mots de
 * passe placent leurs menus correctement. */
.rehaut{
  position:absolute; inset:0; z-index:0; pointer-events:none; border-radius:inherit;
  opacity:var(--anim);
  background:radial-gradient(420px circle at
    calc((var(--px) - .5) * 220% + 50%) calc((var(--py) - .5) * 220% + 50%),
    color-mix(in oklab,var(--accent) 22%,transparent) 0%,transparent 58%);
}

.marque{display:flex; align-items:center; justify-content:center}
/* Hauteur imposee, largeur deduite : le rapport du logo officiel est de
   6,39:1, et le figer des deux cotes le deformerait au premier changement de
   fichier. Les dimensions intrinseques sont portees en attributs sur l'image,
   ce qui evite le saut de mise en page pendant son chargement. */
.logo{height:44px; width:auto; max-width:100%; display:block}
.role{margin:.9rem 0 0; text-align:center; font-size:.8125rem;
      line-height:1.45; color:var(--muted)}
.separateur{height:1px; margin:1.5rem 0 0;
  background:linear-gradient(90deg,transparent,var(--border-strong),transparent)}

form{margin-top:1.4rem}
label{display:block; margin-bottom:.35rem; font-size:.8125rem;
      font-weight:600; color:var(--ink-2)}
.champ{position:relative}
.champ + .champ{margin-top:1rem}
input[type=text],input[type=password],input[type=email]{
  width:100%; padding:.68rem .8rem; font-family:inherit; font-size:.9375rem;
  color:var(--ink); background:var(--surface-2);
  border:1px solid var(--border-strong); border-radius:var(--r-sm);
  transition:border-color 150ms var(--ease),box-shadow 150ms var(--ease);
}
input[type=password]{padding-right:4.25rem}
input::placeholder{color:var(--muted); opacity:.72}
input:hover{border-color:var(--accent)}
input:focus-visible{outline:none; border-color:var(--accent);
                    box-shadow:0 0 0 3px var(--accent-soft)}

/* Chrome impose son propre fond sur les champs remplis automatiquement, ce
 * qui detruirait la carte translucide. La seule surcharge fiable est une ombre
 * interieure massive ; la transition interminable empeche le fond natif de
 * reapparaitre apres coup. */
input:-webkit-autofill,input:-webkit-autofill:hover,input:-webkit-autofill:focus{
  -webkit-box-shadow:inset 0 0 0 1000px var(--surface-2);
  -webkit-text-fill-color:var(--ink);
  caret-color:var(--ink);
  transition:background-color 5000s ease-in-out 0s;
}

.voir{
  position:absolute; right:.35rem; bottom:.3rem; padding:.3rem .5rem;
  font:inherit; font-size:.75rem; font-weight:600;
  color:var(--accent-ink); background:none; border:0;
  border-radius:var(--r-sm); cursor:pointer;
}
.voir:hover{background:var(--accent-soft)}

button[type=submit]{
  width:100%; margin-top:1.4rem; padding:.72rem 1rem;
  font:inherit; font-size:.9375rem; font-weight:600;
  color:var(--btn-pri-ink); background:var(--btn-pri-bg);
  border:0; border-radius:var(--r-sm); cursor:pointer;
  transition:filter 150ms var(--ease),transform 150ms var(--ease);
}
button[type=submit]:hover{filter:brightness(1.08)}
button[type=submit]:active{transform:translateY(1px)}
button[type=submit][aria-disabled=true]{opacity:.65; cursor:progress}

/* Anneau de focus BI-TON. Un anneau d'une seule couleur disparait des qu'il
 * passe au-dessus d'une zone de meme valeur - et le fond, ici, bouge. Le
 * couple clair interieur / sombre exterieur reste lisible sur n'importe quoi. */
:focus-visible{
  outline:2px solid #fff; outline-offset:2px;
  box-shadow:0 0 0 5px rgba(0,0,0,.55); border-radius:6px;
}
input:focus-visible{
  outline:2px solid var(--accent); outline-offset:1px;
  box-shadow:0 0 0 3px var(--accent-soft),0 0 0 5px rgba(0,0,0,.35);
}

/* Rangee sous le mot de passe : le lien d'oubli, aligne a droite. */
.rangee-lien{display:flex; justify-content:flex-end; margin-top:.5rem}
.lien{font-size:.8125rem; font-weight:600; color:var(--accent-ink);
      text-decoration:none}
.lien:hover{text-decoration:underline}

.erreur{
  display:flex; gap:.55rem; align-items:flex-start; margin:1.25rem 0 0;
  padding:.7rem .85rem; font-size:.8125rem; line-height:1.45;
  color:var(--crit); background:var(--crit-soft);
  border:1px solid color-mix(in srgb,var(--crit) 32%,transparent);
  border-radius:var(--r-sm);
}
.erreur svg{flex:none; margin-top:.1rem}
.note{
  margin:1.25rem 0 0; padding:.7rem .85rem; font-size:.8125rem; line-height:1.5;
  color:var(--ink-2); background:var(--accent-soft);
  border:1px solid color-mix(in srgb,var(--accent) 26%,transparent);
  border-radius:var(--r-sm);
}
.note strong{color:var(--ink); font-weight:600}
.note + .note{margin-top:.6rem}

input.code{
  font-family:var(--mono); font-size:1.35rem; font-variant-numeric:tabular-nums;
  letter-spacing:.42em; text-indent:.42em; text-align:center; padding:.6rem .5rem;
}
.aide{margin:.4rem 0 0; font-size:.75rem; color:var(--muted)}
.retour{display:block; margin-top:1.15rem; text-align:center;
        font-size:.8125rem; color:var(--muted); text-decoration:none}
.retour:hover{color:var(--accent-ink); text-decoration:underline}

/* Separateur « ou », pour les modes de connexion complementaires. */
.ou{display:flex; align-items:center; gap:.75rem; margin:1.15rem 0 .1rem;
    font-size:.75rem; color:var(--muted)}
.ou::before,.ou::after{content:""; flex:1; height:1px; background:var(--border-strong)}
.bouton-tiers{
  width:100%; margin-top:.9rem; padding:.66rem 1rem;
  display:flex; align-items:center; justify-content:center; gap:.6rem;
  font:inherit; font-size:.9375rem; font-weight:600; color:var(--ink);
  background:var(--surface-2); border:1px solid var(--border-strong);
  border-radius:var(--r-sm); cursor:pointer; text-decoration:none;
}
.bouton-tiers:hover{border-color:var(--accent)}

footer{
  position:relative; z-index:1;
  padding:0 1.25rem 1.4rem; text-align:center;
  text-shadow:0 1px 2px color-mix(in oklab,#ffffff calc(var(--clarte)*100%),
                                  rgba(0,0,0,.6));
  font-size:.75rem; color:var(--pied);
  display:flex; gap:1rem; align-items:center; justify-content:center;
  flex-wrap:wrap;
}
.bascule{
  font:inherit; font-size:.75rem; color:inherit; background:none;
  border:1px solid var(--border-strong); border-radius:999px;
  padding:.2rem .7rem; cursor:pointer; opacity:.85;
}
.bascule:hover{opacity:1}

/* PLUS D'ANIMATION SUR LA MARQUE. L'ancienne - la nappe d'eau qui montait dans
 * la goutte - etait liee au dessin qu'elle animait, et ce dessin a laisse place
 * au logo officiel. Rien ne l'a remplacee : la scene EST desormais la signature
 * de la page, et lui ajouter un second effet d'entree reviendrait a en avoir
 * deux. Devant le miroir, on retire un accessoire. */

/* 7. MOUVEMENT REDUIT ==================================================== */

/* « Plus calme », et non « eteint ». Ce qui declenche les troubles
 * vestibulaires, c'est le DEPLACEMENT differentiel entre les plans - donc la
 * parallaxe, qu'on annule (--anim:0). Un champ de couleur qui respire
 * lentement, lui, ne pose pas de probleme : les ponts racleurs et les rotors
 * continuent, simplement plus lentement. Tout eteindre serait plus simple, et
 * plus pauvre pour ceux qui n'en avaient pas besoin. */
html[data-mouvement="reduit"]{--anim:0}
html[data-mouvement="reduit"] .scene .pont,
html[data-mouvement="reduit"] .scene .rotor{animation-duration:calc(var(--periode,6s) * 6)}
html[data-mouvement="reduit"] .eol-tete{animation:none}
html[data-mouvement="reduit"] .scene .courant-elec{
  animation-duration:calc(var(--periode,5s) * 4)}
html[data-mouvement="reduit"] .scene .pont-roulant,
html[data-mouvement="reduit"] .scene .bulle,
html[data-mouvement="reduit"] .scene .ecume,
html[data-mouvement="reduit"] .scene .flamme,
html[data-mouvement="reduit"] .scene .helice,
html[data-mouvement="reduit"] .aurore-css .ruban,
html[data-mouvement="reduit"] .flux path,
html[data-mouvement="reduit"] .foyer{animation:none}

@media (prefers-reduced-motion:reduce){
  :root{--anim:0}
  .aurore-css .ruban,.flux path,.foyer{animation:none}
  .scene .pont,.scene .rotor{
    animation-duration:calc(var(--periode,6s) * 6)}
  .eol-tete{animation:none}
  .scene .courant-elec{animation-duration:calc(var(--periode,5s) * 4)}
  .scene .pont-roulant,.scene .bulle,.scene .ecume,
  .scene .flamme,.scene .helice{animation:none}
  button[type=submit],input{transition:none}
}

/* Sans pointeur fin, la parallaxe n'a pas de source : la scene garde son
 * cadrage compose et seule l'animation propre aux ouvrages subsiste. */
@media (hover:none),(pointer:coarse){
  :root{--anim:0}
  .lueur{display:none}
}

@media (prefers-reduced-transparency:reduce){
  .carte{backdrop-filter:none; -webkit-backdrop-filter:none}
  html[data-palette="sombre"] .carte{background:#0e1622}
  html[data-palette="claire"] .carte{background:#ffffff}
}

/* 8. PETITS ECRANS ======================================================= */

@media (max-width:460px){
  /* La carte RESTE une carte. Une version anterieure la passait en pleine
     largeur sans coins ni bordures laterales, mais elle flottait toujours au
     milieu de l'ecran : ni carte, ni plein ecran, donc l'air d'un rendu
     inacheve. On se contente de resserrer les marges. */
  .dedans{padding:1.6rem 1.3rem 1.3rem}
  main{padding:1.25rem .875rem .5rem}
  .logo{height:38px}
  /* Sur un ecran etroit, la scene est essentiellement cachee par la carte :
     on la recadre pour que la station reste dans le champ visible. */
  svg.scene{transform:scale(1.5); transform-origin:64% 78%}
  footer{padding-bottom:1rem}
}
@media (max-height:620px){
  main{padding-top:1rem}
  .logo{height:36px}
  .dedans{padding-top:1.4rem}
}
