/* Styles partagés par la home page anglaise (index.html) et française (fr/index.html).
   Les URLs d'images sont relatives à CE fichier (site/), donc valables pour les deux. */

:root{
  --ink:      #3d4c60;   /* "N" du logo, titres */
  --slate:    #7e93ad;   /* "ABIUH MAPS" du logo, texte secondaire */
  --line:     #6d84a3;   /* trait du compas / accents */
  --paper:    #ffffff;
  --paper-2:  #f6f8fa;   /* fond des cartes/sections alternées */
  --border:   #e2e8f0;
  --text:     #33475b;
  /* #6b7c93 auparavant : 4,26:1 sur blanc, sous le seuil WCAG AA (4,5:1), et
     ~3,9:1 une fois le fond d'affiche derrière. Assombri à 5,85:1 / 4,67:1. */
  --text-dim: #546680;
}
*{ box-sizing:border-box; }
html,body{ margin:0; padding:0; }
body{
  position:relative;
  background:var(--paper);
  color:var(--text);
  font-family:"Space Grotesk", system-ui, sans-serif;
  line-height:1.45;
  -webkit-font-smoothing:antialiased;
}
/* Fond atténué : relief en vue oblique (clin d'œil HWF/HIR), fixe derrière
   tout le contenu. VECTORIEL, et pas un PNG : celui d'origine ne faisait que
   1906 px de large et devait être étiré en `cover`, d'où un trait grossier et
   pixelisé. Le SVG reste net à n'importe quelle taille et permet un trait
   hairline impossible à tenir en bitmap.
   Le tracé n'est PAS un terrain synthétique : plusieurs essais de relief
   généré (bruit fractal, isocontours projetés en 3D) sonnaient artificiels.
   C'est la VECTORISATION de la carte d'isolignes fournie par l'utilisateur —
   un vrai relief. Chaîne de traitement, si un jour il faut la refaire :
   binarisation (luminance < 205), squelettisation Zhang-Suen pour ramener
   chaque trait à 1 px — sinon chaque ligne donnerait DEUX courbes, une de
   chaque côté — puis suivi du squelette, recousage des extrémités distantes
   de moins de 4 px (sans quoi le tracé se casse en pointillés à chaque
   jonction : 3 288 fragments contre 70 polylignes après couture) et
   simplification Douglas-Peucker.
   ⚠️ ORDRE DES ÉTAPES : découper les sauts de couture abusifs AVANT de
   simplifier, jamais après. Après simplification, une longue portion droite
   est réduite à deux points très éloignés, indiscernable d'un saut : on
   supprime alors de vrais segments. Mesuré, ce seul détail laissait 11,9 % du
   squelette sans tracé ; dans le bon ordre, 0,03 %.
   Lissage BORNÉ EN ÉCHELLE, et c'est tout : noyau [1,2,1]/4, 2 passes, puis
   Douglas-Peucker à 0,35 px. Rayon effectif ~1 px — de quoi effacer le
   crénelage du squelette, trop court pour atteindre un détail de terrain qui
   s'étend sur 10 px ou plus.
   ⚠️ Ne PAS empiler les passes de lissage : le rayon du filtre grandit à
   chaque passe. À 8 passes, les moyennes échelles disparaissent avec les
   petites, et le relief devient une suite de courbes molles. Toute la
   détection d'angles essayée avant n'était qu'un pansement sur ce défaut —
   inutile dès lors qu'on filtre par échelle et non par géométrie. */
body::before{
  content:""; position:fixed; inset:0;
  background:url("assets/bg-contours.svg") center / cover no-repeat;
  /* Trait de 0,5 unité, soit du sous-pixel à l'écran : l'antialiasing
     l'atténue déjà beaucoup, d'où une opacité plus haute qu'on ne
     l'imaginerait pour un motif aussi discret. */
  opacity:0.15;
  z-index:-1;
  pointer-events:none;
}
a{ color:var(--ink); }
.wrap{ max-width:1280px; margin:0 auto; padding:0 calc(16px + 16px * var(--t, 0)); }

/* ---- hero / logo -------------------------------------------------------
   Tout est calc(MIN + (MAX-MIN) * var(--t)), où --t (0 à 1) est calculé en
   JS à partir de la largeur ET la hauteur RÉELLES de la fenêtre (voir
   app.js) : t = min(progression en largeur, progression en hauteur).
   Une fenêtre large mais pas haute (laptop maximisé) est donc limitée par sa
   hauteur, pas par sa largeur — plus de scrollbar surprise. --t=0 = valeurs
   resserrées (tient sur iPad/petit laptop), --t=1 = valeurs spacieuses. */
.hero{
  padding:
    calc(20px + 26px * var(--t, 0))
    0
    calc(14px + 22px * var(--t, 0));
  text-align:center;
}
.hero .logo-big{ width:min(calc(460px + 300px * var(--t, 0)), 92vw); height:auto; margin:0 auto calc(10px + 13px * var(--t, 0)); display:block; }
.hero h1{
  margin:0 0 calc(8px + 6px * var(--t, 0));
  font-family:Helvetica, Arial, sans-serif;
  font-size:calc(24px + 18px * var(--t, 0));
  font-weight:700;
  color:var(--ink);
  letter-spacing:0.01em;
}
.hero p.tagline{
  margin:0 auto; max-width:min(calc(560px + 60px * var(--t, 0)), 85vw);
  font-size:calc(15px + 3px * var(--t, 0)); color:var(--text-dim);
}

/* ---- sections générales ---------------------------------------------- */
section{ padding:calc(20px + 13px * var(--t, 0)) 0; }
section.alt{ background:var(--paper-2); border-top:1px solid var(--border); border-bottom:1px solid var(--border); }
p{ margin:0 0 14px; }

/* ---- carte projet ------------------------------------------------------ */
/* Trois colonnes depuis le retrait des deux séries « Coming soon » : la grille
   suit le nombre de cartes en vente. Les cartes passent de 186 à 320 px de
   large à 1024 px, ce qui change tout l'équilibre du dégradé — voir la note
   sur le masque plus bas, remesurée pour cette largeur. */
.projects-row{ display:grid; grid-template-columns:repeat(3, 1fr); gap:calc(16px + 4px * var(--t, 0)); align-items:stretch; }
.project-card{
  position:relative; overflow:hidden;   /* le fond est rogné par le rayon */
  border:1px solid var(--border); border-radius:14px;
  background:var(--paper);
  display:flex; flex-direction:column;
}
/* Survol repris de cts.nabiuhmaps.com (`.card:hover`) : même levée de 4 px et
   même ombre, pour que les deux sites réagissent pareil. La teinte de l'ombre
   est `--ink` à 8 %, la couleur que CTS y met déjà en dur.
   ⚠️ Seules les collections EN LIGNE réagissent : lever une carte « bientôt
   disponible » promettrait un clic qui n'existe pas (elles n'ont pas de lien).
   ⚠️ L'ombre déborde de la carte : aucun ancêtre ne doit rogner. Vérifié ici.
   Chez CTS le piège avait mordu — `overflow-x:auto` sur la ligne de thèmes
   force `overflow-y:auto` (règle CSS : un seul axe non-`visible` suffit), et
   l'ombre se faisait couper en haut. */
.card-cts, .card-hwf, .card-hir{
  transition:transform 0.2s ease, box-shadow 0.2s ease;
}
.card-cts:hover, .card-hwf:hover, .card-hir:hover{
  transform:translateY(-4px);
  box-shadow:0 8px 24px rgba(61, 76, 96, 0.08);
}
@media (prefers-reduced-motion: reduce){
  .card-cts, .card-hwf, .card-hir{ transition:none; }
  .card-cts:hover, .card-hwf:hover, .card-hir:hover{ transform:none; }
}
/* Fond estompé : un extrait d'affiche de la collection, très atténué. Posé en
   ::before plutôt qu'en background de la carte, pour que l'opacité ne touche
   QUE l'image — sur la carte elle-même, elle décolorerait aussi le texte.
   Le dégradé de masque l'efface vers le bas : le bouton reste sur du blanc. */
.card-cts::before,
.card-hwf::before,
.card-hir::before{
  content:""; position:absolute; inset:0;
  background:var(--card-bg) center / cover no-repeat;
  opacity:1;
  /* L'estompage DÉCROÎT en descendant : un voile ténu derrière le texte,
     l'image pleine au niveau du bouton — celui-ci est un aplat opaque, donc
     insensible à ce qu'il y a derrière.
     Dégradé ancré en PIXELS DEPUIS LE BAS, pas en pourcentage : le bouton est
     toujours à la même distance du bas (margin-top:auto), alors que le
     paragraphe s'étire. Un dégradé en pourcentage repassait derrière les
     dernières lignes.

     ⚠️ CE QUI PLAFONNE CE DÉGRADÉ, C'EST LA COULEUR DU PARAGRAPHE, pas la forme
     du masque. Mesuré (pire fond local sous les rectangles de ligne RÉELS) :
     à texte #546680 l'opacité maximale tenant 4,5:1 est 0,55 ; à #43536e elle
     monte à 1,09 — presque le double de présence d'image pour un texte qui est
     par ailleurs PLUS lisible. D'où le duo : paragraphe assombri (cf.
     `.project-card p`) et masque intensifié d'autant.

     ⚠️ Méthode : mesurer sur la BOÎTE du <p> donne un résultat faux. Elle a
     `flex:1 0 auto` et descend toujours à ~75 px du bas, alors que le texte
     s'arrête à 76 px sur HIR mais 118 px sur HWF (description plus courte).
     Juger sur la boîte pénalise donc l'image là où il n'y a personne à gêner,
     et fait conclure à tort que le réglage échoue. C'est HIR qui contraint —
     c'est sa description, la plus longue, qui fixe le plafond pour les trois.

     ⚠️ Les arrêts SUIVENT --t, ils ne sont pas fixes. Le bas du texte de HIR
     tombe à 77 px du bas de carte à --t=0,56 mais à **62 px** à --t=0 (le bouton
     rétrécit), alors que le dégradé, lui, était ancré au bas de la carte. La
     dernière ligne se retrouvait donc dans la partie forte : mesuré, HIR à
     **3,36:1** à 1024 px avec le réglage d'avant — il ne tenait qu'aux grandes
     largeurs. Le décalage `27px * var(--t)` fait suivre la rampe au texte.

     ⚠️ Le coefficient de --t (48px) est plus fort que celui d'origine (27px)
     parce que la marge sous le paragraphe grandit elle aussi avec --t : la
     rampe doit suivre la remontée du texte, sinon elle laisse un voile inutile
     là où l'image pourrait être pleine.

     ⚠️ C'EST LA PAGE FRANÇAISE QUI CONTRAINT, et la carte HWF. Sa description
     est plus longue en français qu'en anglais (12 lignes contre 9 à 1024 px),
     donc son texte descend plus bas ; et son illustration est la plus sombre
     des trois. Régler sur la seule page anglaise donne un faux positif : au
     2026-09-07, un texte HWF rallongé faisait tomber la version FR à 4,16:1
     (1024 px) et 4,35:1 (1348 px) alors que l'anglaise restait à 4,85.
     **Toute retouche de ces descriptions impose de remesurer en FR.**

     ⚠️ Méthode : mesurer sur la BOÎTE du <p> donne un résultat faux. Elle a
     `flex:1 0 auto` et descend toujours au même endroit, alors que le texte
     s'arrête à 77 px sur HIR et 119 px sur HWF (description plus courte). Juger
     sur la boîte pénalise l'image là où il n'y a personne à gêner. C'est HIR,
     la description la plus longue, qui fixe le plafond pour les trois. */
  -webkit-mask-image:linear-gradient(to top, rgba(0,0,0,1) 0px, rgba(0,0,0,.92) calc(10px + 50px * var(--t, 0)), rgba(0,0,0,.31) calc(40px + 50px * var(--t, 0)), rgba(0,0,0,.135) calc(92px + 50px * var(--t, 0)), rgba(0,0,0,.052) calc(162px + 50px * var(--t, 0)), rgba(0,0,0,0) calc(242px + 50px * var(--t, 0)));
          mask-image:linear-gradient(to top, rgba(0,0,0,1) 0px, rgba(0,0,0,.92) calc(10px + 50px * var(--t, 0)), rgba(0,0,0,.31) calc(40px + 50px * var(--t, 0)), rgba(0,0,0,.135) calc(92px + 50px * var(--t, 0)), rgba(0,0,0,.052) calc(162px + 50px * var(--t, 0)), rgba(0,0,0,0) calc(242px + 50px * var(--t, 0)));
  pointer-events:none;
}
.project-card .body{ position:relative; }  /* au-dessus du fond */
.card-cts{ --card-bg:url("assets/bg-cts.jpg"); }
.card-hwf{ --card-bg:url("assets/bg-hwf.jpg"); }
.card-hir{ --card-bg:url("assets/bg-hir.jpg"); }
.project-card .body{ padding:calc(13px + 8px * var(--t, 0)) calc(18px + 6px * var(--t, 0)); display:flex; flex-direction:column; height:100%; }
.project-card h2{ margin:0 0 calc(4px + 4px * var(--t, 0)); font-family:Helvetica, Arial, sans-serif; font-size:calc(17px + 2px * var(--t, 0)); color:var(--ink); }
.project-card .kicker{
  font-family:"JetBrains Mono", monospace; font-size:calc(11px + 1px * var(--t, 0)); letter-spacing:0.08em;
  text-transform:uppercase; color:var(--slate); margin-bottom:calc(4px + 4px * var(--t, 0)); display:block;
}
/* Assombri par rapport à --text-dim (#546680) : c'est CE réglage qui autorise
   le fond d'affiche à être deux fois plus présent, l'image étant plafonnée par
   le contraste du texte qu'elle traverse. 7,77:1 sur blanc contre 5,85:1 — donc
   plus lisible qu'avant, pas moins. Volontairement local à la carte : --text-dim
   sert aussi au pied de page, qui n'a aucune image derrière lui. */
/* ⚠️ La marge BASSE n'est pas décorative : c'est elle qui écarte la dernière
   ligne du bas de carte, donc qui libère la bande où l'illustration peut se
   voir. Mesuré à 1348 px : la porter de 12 à 29 px fait passer le pire
   contraste de 4,35 à 4,94 ET la présence de l'image de 25,1 à 29,4 — les deux
   progressent, alors qu'affaiblir le dégradé les oppose.
   ⚠️ Elle croît avec --t parce que le jeu vertical disponible en dépend :
   mesuré à 1024×700 il ne reste que ~20 px sous le pied de page, contre ~90 px
   à 1348×932. D'où une base volontairement modeste (13 px).
   ⚠️ À --t=0 elle reste à 10 px, sa valeur d'origine, et il ne faut PAS la
   monter « tant qu'il reste du jeu » : la mise en page est BISTABLE près du
   seuil. Dès que la barre de défilement apparaît elle prend ~15 px de largeur,
   le texte se replie sur une ligne de plus, les cartes grandissent — ce qui
   confirme la barre. Testé : 13 px suffisent à faire basculer la page au
   chargement, alors qu'injecter la même valeur sur une page déjà stabilisée
   sans barre ne la fait pas basculer. Mesurer sur une page déjà chargée donne
   donc un faux feu vert ; il faut recharger. À --t=0 c'est la rampe du dégradé,
   et non la marge, qui sert de réglage. */
.project-card p{ color:#43536e; font-size:calc(13.5px + 1.5px * var(--t, 0)); margin:0 0 calc(10px + 29px * var(--t, 0)); flex:1 0 auto; }

@media (max-width: 980px){
  .projects-row{ grid-template-columns:1fr; }
  /* En colonne unique la carte devient large et COURTE (163 px de haut mesurés
     à 900 px de large, contre 336 en grille) : la rampe ci-dessus s'étale sur
     258 px, donc plus que la carte entière — l'image y passait à pleine force
     derrière tout le texte. Mesuré, HWF tombait à 2,60:1. Rampe raccourcie,
     qui redonne 4,81:1 tout en montrant PLUS d'image qu'avant (présence 42,5
     contre 38,0) : ces cartes ont beaucoup de largeur libre à droite du texte. */
  .card-cts::before, .card-hwf::before, .card-hir::before{
    -webkit-mask-image:linear-gradient(to top, rgba(0,0,0,1) 0px, rgba(0,0,0,.85) 26px, rgba(0,0,0,.26) 52px, rgba(0,0,0,.10) 86px, rgba(0,0,0,.04) 130px, rgba(0,0,0,0) 170px);
            mask-image:linear-gradient(to top, rgba(0,0,0,1) 0px, rgba(0,0,0,.85) 26px, rgba(0,0,0,.26) 52px, rgba(0,0,0,.10) 86px, rgba(0,0,0,.04) 130px, rgba(0,0,0,0) 170px);
  }
}
/* Bouton BLANC et non plus en aplat sombre : posé sur la partie pleine de
   l'illustration, un pavé foncé pesait autant qu'elle et lui disputait le
   regard. En blanc il se détache par contraste inverse et laisse l'image
   première.
   ⚠️ Le fond doit rester OPAQUE : à cet endroit le masque du ::before est à 1,
   donc l'image passe à pleine force. Un blanc translucide laisserait le motif
   traverser le libellé.
   ⚠️ Le filet est indispensable — sans lui, un bouton blanc posé sur une zone
   claire de l'affiche (le plan de rues de CTS, presque blanc) perdrait tout
   contour. Mesuré, --line donne 3,83:1 sur blanc, au-dessus du seuil de 3:1
   exigé pour un élément d'interface non textuel ; le libellé est à 8,75:1.
   ⚠️ Filet en box-shadow INSET et non en `border` : une bordure ajoute 2 px à
   la hauteur du bouton, ce qui suffit à faire apparaître la barre de défilement
   à 1024×700 (cf. la bistabilité décrite plus haut). L'anneau inset se dessine
   à l'intérieur de la boîte et ne coûte rien en mise en page.
   Au survol, on retrouve l'aplat sombre d'origine : l'inversion signale
   l'action sans introduire de couleur nouvelle. */
.btn{
  display:inline-flex; align-items:center; gap:8px;
  margin-top:calc(2px + 8px * var(--t, 0)); padding:calc(8px + 2px * var(--t, 0)) calc(16px + 2px * var(--t, 0));
  background:var(--paper); color:var(--ink); text-decoration:none;
  box-shadow:inset 0 0 0 1px var(--line);
  border-radius:8px; font-weight:600; font-size:calc(13px + 1px * var(--t, 0));
  width:fit-content;
  transition:background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
}
.btn:hover{ background:var(--ink); color:var(--paper); box-shadow:inset 0 0 0 1px var(--ink); }
.btn .arrow{ font-family:"JetBrains Mono", monospace; }

/* CARTE ENTIÈRE CLIQUABLE. La carte se soulève déjà au survol : elle se
   présente comme un objet cliquable, il fallait qu'elle le soit vraiment.
   Le lien du bouton est ÉTIRÉ par un ::after transparent plutôt que par un <a>
   enveloppant toute la carte :
   - un seul lien par carte, donc une seule tabulation et un seul libellé lus
     par un lecteur d'écran (envelopper aurait dupliqué la cible, ou noyé le
     libellé sous tout le texte de la carte) ;
   - aucun changement de mise en page (position absolue), là où un <a> bloc
     autour du contenu aurait rouvert la question de la hauteur des cartes.
   ⚠️ L'ancre est `.body` (position:relative), pas la carte : c'est le plus
   proche ancêtre positionné. Il occupe toute la carte (height:100%, la marge
   intérieure est comptée dedans), la surface cliquable est donc bien la boîte.
   ⚠️ Le ::before du fond est déjà en pointer-events:none, sinon il capterait
   les clics à la place du lien.
   Conséquence assumée : le texte de la carte n'est plus sélectionnable à la
   souris — c'est le prix de tout « stretched link ». */
.card-cts .btn::after,
.card-hwf .btn::after,
.card-hir .btn::after{ content:""; position:absolute; inset:0; }
/* Survol du bouton quand on est ailleurs sur la carte. Le :hover remonte déjà
   du ::after vers le bouton qui l'engendre, mais on l'écrit aussi en clair :
   le lien logique entre les deux ne se devine pas à la lecture. */
.card-cts:hover .btn,
.card-hwf:hover .btn,
.card-hir:hover .btn{ background:var(--ink); color:var(--paper); box-shadow:inset 0 0 0 1px var(--ink); }

/* ---- projet à venir (MSR, CFA) --------------------------------------- */
.project-card.soon{ opacity:0.72; }
.project-card.soon .kicker{ color:var(--text-dim); }
/* Projet encore secret : titre + description remplacés par une image dont le
   flou est cuit dans les pixels — contrairement à un filter:blur() CSS, qui
   laisse le texte en clair dans le HTML (source, DOM, aperçus de partage).
   Textes sources et script de regénération : ../tools/teasers.md
   L'image se comporte comme le bloc titre+paragraphe qu'elle remplace :
   largeur de la colonne, hauteur naturelle, badge poussé en bas. Elle porte
   une marge transparente (--pad) où le flou s'estompe au lieu d'être tranché
   net au bord ; les marges négatives annulent ce débord, donc le texte reste
   aligné au pixel près sur celui des cartes CTS/HWF. */
.soon-teaser{
  --pad: 12px;
  display:block; width:calc(100% + 2 * var(--pad)); height:auto;
  flex:0 0 auto;
  margin:calc(0px - var(--pad)) calc(0px - var(--pad)) calc(10px + 4px * var(--t, 0) - var(--pad));
  user-select:none; pointer-events:none;
}
.badge-soon{
  display:inline-flex; align-items:center; margin-top:auto;
  padding:calc(7px + 2px * var(--t, 0)) calc(14px + 2px * var(--t, 0)); border:1px solid var(--border); border-radius:8px;
  font-size:calc(13px + 1px * var(--t, 0)); font-weight:600; color:var(--text-dim); width:fit-content;
}

/* ---- footer ------------------------------------------------------------ */
footer{
  padding:
    calc(12px + 17px * var(--t, 0))
    0
    calc(12px + 17px * var(--t, 0));
  text-align:center;
}
footer .credit{
  font-family:"JetBrains Mono", monospace; font-size:calc(11px + 1px * var(--t, 0));
  color:var(--text-dim); letter-spacing:0.02em;
}
/* le N en gras, comme dans le logo. La graisse 700 de JetBrains Mono est
   chargée exprès : sans elle le navigateur fabriquerait un faux gras, pâteux
   sur une monospace de 11 px. */
footer .credit .n{ font-weight:700; }
/* l'année reste neutre : ne suit pas la couleur aléatoire du logo (appliquée
   par app.js sur #footerCredit, donc héritée par ses enfants sinon) */
#creditYear{ color:var(--text-dim); }
/* Contact sur la ligne du crédit. La rangée de rappels des collections a été
   retirée : elle redisait les trois cartes situées juste au-dessus, à un écran
   de distance, et c'est le seul lien du pied de page qui n'était nulle part
   ailleurs qui restait à placer.
   ⚠️ Même précaution que #creditYear : la couleur aléatoire du logo est posée
   par app.js sur #footerCredit et serait héritée par le lien comme par le
   séparateur. Les deux reprennent donc explicitement les tons du pied de page. */
footer .credit .sep,
footer .legal .sep{ color:var(--border); margin:0 6px; }
/* Ligne des documents juridiques, entre le crédit et le sélecteur de langue.
   ⚠️ Volontairement HORS de #footerCredit : app.js y pose une teinte tirée au
   hasard, que ces liens auraient héritée. Ici la question ne se pose pas —
   c'est aussi pourquoi elle n'a pas besoin du correctif de couleur que porte
   le crédit.
   Les liens portent le TITRE EXACT de chaque document, sans abréviation :
   « CGV » et « Terms » sont des raccourcis, et un lien vers un document
   juridique doit nommer ce qu'il ouvre. La ligne est longue en conséquence —
   d'où le repli centré ci-dessous, pour qu'elle reste lisible quand la fenêtre
   ne peut plus la tenir d'un seul tenant. */
footer .legal{ margin-top:calc(6px + 4px * var(--t, 0)); font-size:calc(11px + 1px * var(--t, 0)); line-height:1.7; }
footer .legal a{ color:var(--text-dim); text-decoration:none; }
footer .legal a:hover{ color:var(--ink); text-decoration:underline; }
footer .credit a{ color:var(--text-dim); text-decoration:none; }
footer .credit a:hover{ color:var(--ink); text-decoration:underline; }
/* Icônes de contact. Dimensionnées en `em` : elles suivent la taille du crédit,
   qui varie avec --t, au lieu d'être figées en pixels et de grossir ou maigrir
   par rapport au texte selon la fenêtre.
   ⚠️ Le trait est en `currentColor` et NON en couleur fixe : le crédit reçoit
   d'app.js une teinte tirée au hasard, mais ces liens la neutralisent (cf.
   ci-dessus) — `currentColor` les fait donc suivre --text-dim puis --ink au
   survol, sans règle de survol dédiée.
   ⚠️ vertical-align et non un inline-flex sur le lien : le crédit est une seule
   ligne de texte où les séparateurs doivent rester sur la même assise. Un
   inline-flex fabriquerait une boîte alignée autrement et décalerait le « · ».
   Le retrait de 0,18em pose l'icône sur l'axe des capitales, pas sur la ligne
   de base — mesuré à l'œil sur JetBrains Mono, qui n'a pas de jambage ici.
   ⚠️ Le trait est épaissi à 1,4 dans un viewBox de 16 : réduit avec la forme,
   il vaut ~1 px à l'écran, soit la graisse du texte à côté. Le laisser à 1
   donnerait ~0,7 px, que l'antialiasing dilue en gris pâle — l'icône
   paraîtrait alors plus claire que le crédit qu'elle accompagne. */
footer .credit .ico{
  width:1.3em; height:1.3em; vertical-align:-0.33em;
  fill:none; stroke:currentColor; stroke-width:1.4;
  stroke-linecap:round; stroke-linejoin:round;
}
/* ⚠️ Les icônes portent le lien SEULES, sans libellé : à 1,3 em elles font
   ~14 px, très en dessous des 24 px de cible recommandés (WCAG 2.5.8). Le
   rembourrage agrandit donc la zone cliquable à ~28 px sans rien changer à la
   mise en page : sur un élément EN LIGNE, le rembourrage vertical déborde de la
   boîte de ligne sans l'agrandir — il compte pour le pointeur, pas pour la
   hauteur du pied de page. Un inline-block ou un inline-flex, lui, aurait
   poussé le pied de page vers le bas.
   Le rembourrage horizontal sert AUSSI d'espacement : 6 px de chaque côté
   donnent 12 px entre les deux icônes, et autant après le séparateur, dont la
   marge vaut elle aussi 6 px. Pas de marge en plus, qui doublerait l'écart. */
footer .credit .ico-link{ padding:7px 6px; }

/* sélecteur de langue : la langue courante est en évidence, l'autre cliquable */
footer .lang{ margin-top:calc(5px + 3px * var(--t, 0)); font-size:calc(11px + 1px * var(--t, 0)); color:var(--border); }
footer .lang a{ color:var(--text-dim); text-decoration:none; margin:0 6px; }
footer .lang a:hover{ color:var(--ink); text-decoration:underline; }
footer .lang [aria-current]{ color:var(--ink); font-weight:600; margin:0 6px; }
