/* =================================================================
   Youen Frémont — Menu plein écran et ses animations
   Feuille 6 sur 8. La feuille unique a été découpée le 2026-09-19 aux
   frontières de ses sections, sans qu'une règle ne change de place : les huit
   fichiers se chargent dans l'ordre de base.njk, et cet ordre EST la cascade.
   Les tokens et les surfaces vivent dans style.css, chargée en premier.
   ================================================================= */

/* -----------------------------------------------------------------
   14. Menu — <dialog> natif : ouvert en modale, il piège le focus, rend le
   reste de la page inerte et se ferme avec Échap sans une ligne de JS.
   Sans JavaScript, on annule les styles du navigateur et il redevient une
   simple liste de liens en fin de page.
   ----------------------------------------------------------------- */
/* Le menu est sur la surface couleur — il porte la classe dans menu.njk. Il
   était le seul bloc du site sur un troisième neutre, un gris à 5 % qui n'était
   ni le papier ni la couleur ; c'est un plein écran d'identité, pas une surface
   de contenu, il prend donc l'aplat de la page. */
.menu {
  width: 100%;
  max-width: none;
  padding: var(--pas-7) var(--marge-page);
  border: 0;
}

html:not(.js) .menu {
  display: block;
  position: static;
}

/* Même boîte que l'en-tête : hauteur, marge latérale et centrage identiques,
   pour que la croix et la pilule tombent sur le bouton menu et le lien contact. */
.menu__barre {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--pas-4);
  min-height: var(--hauteur-entete);
  padding-top: var(--decalage-entete);
}

.menu__nav {
  display: grid;
  place-items: center;
}

.menu__liste {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: var(--texte-menu);
  /* Le menu déplié écrit ses trois entrées à 112 px : à cette taille ce ne sont
     plus des libellés de navigation mais les plus grands caractères de l'écran.
     Ils prennent donc la fonte de titrage, sur ta remarque du 2026-09-08. Le
     numéro suit sur la même ligne : deux fontes sur une même ligne se verraient. */
  font-family: var(--police-titre);
  font-weight: var(--graisse-forte);
  line-height: var(--interligne-titre);
  letter-spacing: var(--approche-titre);
  text-transform: uppercase;
}

.menu__liste a {
  position: relative;
  display: inline-block;
}

/* Pas de soulignement ici : à cette taille le trait déborde sur l'onglet
   suivant. C'est la couleur qui signale l'état, sur le mot comme sur son numéro. */
.menu__liste a:hover,
.menu__liste a:focus-visible,
.menu__liste a[aria-current] {
  color: var(--titre);
  text-decoration: none;
}

/* Le numéro ne se montre qu'au survol, au focus clavier, et sur la page où l'on
   se trouve — c'est là son second rôle : dire où l'on est. Le libellé prend la
   couleur en même temps que lui, décision du 2026-08-19 : la couleur devient un
   repère de navigation et non une décoration, les trois mots restant en encre au
   repos. Un soulignement était exclu — à cette taille il débordait sur l'onglet
   suivant. */
.menu__numero {
  position: absolute;
  right: 100%;
  top: 0.5em;
  font-size: 0.2em;
  letter-spacing: 0;
  opacity: 0;
}

.menu__liste a:hover .menu__numero,
.menu__liste a:focus-visible .menu__numero,
.menu__liste a[aria-current] .menu__numero {
  color: var(--remplissage);
  opacity: 1;
}

.menu__pied {
  display: flex;
  align-items: end;
  justify-content: space-between;
  gap: var(--pas-4);
}

/* Sous 48 em, la mention et les trois liens ne tiennent plus sur une ligne : ils
   s'empilent au centre, les liens d'abord, la mention en dernier — d'où le
   column-reverse, la mention venant en premier dans le HTML. */
@media (max-width: 47.999em) {
  .menu__pied {
    flex-direction: column-reverse;
    align-items: center;
    gap: var(--pas-3);
    text-align: center;
  }

  .liens-sociaux {
    flex-direction: column;
    align-items: center;
    gap: var(--pas-3);
  }
}

.liens-sociaux {
  display: flex;
  flex-wrap: wrap;
  gap: var(--pas-5);
  font-size: var(--texte-s);
  font-weight: var(--graisse-forte);
  letter-spacing: var(--approche-libelle);
  text-transform: uppercase;
}

.liens-sociaux span {
  margin-left: 0.2em;
}

/* Le bouton de fermeture n'a de sens que si le menu s'ouvre. */
html:not(.js) .bouton-fermer {
  display: none;
}

html.js .menu {
  position: fixed;
  inset: 0;
  width: 100%;
  max-width: none;
  height: 100%;
  max-height: none;
  padding: 0;
}

/* Fermé, le navigateur le masque déjà (dialog:not([open])).
   Barre et pied sont sortis du flux : la navigation reste le seul élément placé,
   donc centrée sur la hauteur et la largeur du panneau entier. */
html.js .menu[open] {
  display: grid;
  place-items: center;
}

html.js .menu .menu__barre {
  position: absolute;
  right: var(--marge-page);
  left: var(--marge-page);
  top: 0;
}

html.js .menu .menu__pied {
  position: absolute;
  right: var(--marge-page);
  bottom: var(--marge-page);
  left: var(--marge-page);
}

/* Réserve de place en haut et en bas, symétrique pour ne pas fausser le centre. */
html.js .menu .menu__nav {
  padding: var(--hauteur-entete) var(--marge-page);
}

/* Le panneau couvre tout l'écran : ce fond ne se voit que le temps de son
   arrivée. C'est un voile de noir et non l'aplat : le panneau EST l'aplat, et un
   pan de couleur qui monte sur un fond de la même couleur ne se verrait pas.
   Le voile, lui, détache le panneau de la page quelle que soit la déclinaison —
   et il n'ajoute pas de septième couleur, le noir voilé est dans la charte. */
.menu::backdrop {
  background-color: color-mix(in srgb, var(--noir) 55%, transparent);
}

/* Le fond du panneau vit dans un calque à part : il peut apparaître en fondu
   sans emporter le bouton rond, qui doit rester immobile — il est posé au pixel
   sur celui de l'en-tête — et se contenter de pivoter. */
html.js .menu {
  background-color: transparent;
}

html.js .menu::before {
  position: absolute;
  inset: 0;
  z-index: -1;
  background-color: var(--fond);
  /* Le pan monte par rognage et non par déplacement : déplacé, il sortirait de
     la boîte du <dialog> et pourrait y faire apparaître une barre de défilement
     le temps de l'animation. Rogné, il ne déborde jamais.
     La valeur de repos ne rogne rien — elle n'est là que pour donner à
     l'animation un point d'arrivée interpolable : de `inset()` vers `none`, rien
     ne s'interpole. */
  clip-path: inset(0);
  content: "";
}

@media (prefers-reduced-motion: no-preference) {
  /* L'en-tête change de surface au moment où le panneau arrive sous elle : voile,
     flou et encre basculent ensemble. Sans transition, les trois sautaient d'une
     image à l'autre. Le flou n'est pas interpolable — on le retire donc après le
     fondu de sortie (0s de durée, 0,2s de retard), et on le pose sans attendre à
     l'entrée, où l'opacité encore basse le rend invisible de toute façon. */
  .entete::before {
    transition: opacity 0.25s ease, backdrop-filter 0s ease 0.25s;
  }

  .entete[data-papier]::before,
  .entete--planches[data-defile]::before {
    transition: opacity 0.25s ease, backdrop-filter 0s ease 0s;
  }

  .entete {
    transition: color 0.25s ease;
  }

  .entete .bouton-rond,
  .entete .bouton-pilule {
    transition: background-color 0.25s ease, color 0.25s ease;
  }

  .menu__numero {
    transition: opacity 0.15s ease;
  }

  .carte-projet {
    transition: transform 0.2s ease, box-shadow 0.2s ease;
  }

  .carte-projet:hover,
  .carte-projet:focus-within {
    transform: translateY(-0.375rem);
  }

  /* Apparition au défilement. L'état de départ n'est posé que si le script est
     là pour le lever : sans JavaScript, ou si l'observateur n'existe pas, les
     cartes sont simplement en place. Le décalage d'une colonne sur deux suffit
     à ce que la rangée ne se lève pas d'un bloc.
     Le survol reprend la main sur la transformation une fois la carte vue. */
  html.js .carte-projet {
    transition: opacity 0.6s ease, transform 0.7s cubic-bezier(0.16, 1, 0.3, 1),
      box-shadow 0.2s ease;
  }

  html.js .grille-projets > li:not([data-vue]) .carte-projet,
  html.js .vitrine__liste > li:not([data-vue]) .carte-projet {
    opacity: 0;
    transform: translateY(var(--montee-carte));
  }

  html.js .grille-projets > li:nth-child(2n) .carte-projet {
    transition-delay: 0.08s;
  }

  .carte-projet__pied {
    transition: opacity 0.2s ease, transform 0.2s ease;
  }

  /* Le trait des liens du pied et de la liste de contact se dessine de la
     gauche vers la droite. `background-size` est animable, `text-decoration`
     ne l'est pas — c'est toute la raison du dégradé posé en fond, section 3. */
  .pied a,
  .liste-contact a,
  .liens-sociaux a,
  .mention-legale a {
    transition: background-size 0.25s ease;
  }

  /* Le roulement du titre de la vitrine. --roule est posé par main.js d'après la
     position du cadre entre deux projets : 0 sur l'un, 1 sur l'autre, et tout
     l'entre-deux. Rien n'est animé au sens du navigateur — c'est le doigt, la
     molette ou la touche qui déplacent le titre, exactement comme les cartes.
     La course vaut 140 % et non 100 : à une hauteur pile, les jambages du titre
     sortant reparaîtraient dans la réserve laissée aux accents. */
  .vitrine__encours[data-roulement] > span:last-child {
    visibility: visible;
  }

  .vitrine__encours[data-roulement] > span:first-child {
    transform: translateY(calc(var(--roule, 0) * -140%));
  }

  .vitrine__encours[data-roulement] > span:last-child {
    transform: translateY(calc((1 - var(--roule, 0)) * 140%));
  }

  /* Le nom du hero et la mention de disponibilité cèdent la place au panneau
     blanc qui monte dessus : ils s'effacent et rapetissent à mesure que son bord
     haut traverse l'écran. Miroir du pied de page, qui se découvre par le même
     mouvement à l'autre bout.
     --retrait est posé par main.js sur le bloc qui les contient, de 0 quand le
     panneau est encore hors champ à 1 quand il touche le haut. Sans script, la
     propriété n'existe pas : le repli de 0 les laisse entiers.
     Les filtres de la page Work en sont exclus à dessein : ils prennent le focus
     au clavier, et un élément focalisable qu'on ne voit plus est un piège. */
  .hero__titre,
  .pastille-dispo {
    opacity: calc(1 - var(--retrait, 0));
    transform: scale(calc(1 - var(--retrait, 0) * 0.12));
  }

  /* Le nom s'installe lettre après lettre — c'est main.js qui découpe le titre
     et pose l'attribut. Le générique, lui, recouvre la page et se termine par un
     fondu : les deux ne se voient pas en même temps.
     Aucune lettre n'est cachée au repos : c'est l'animation qui les fait venir,
     en remontant avant son début (`backwards`). Si elle ne joue pas, le nom est
     simplement là. */
  .hero__titre[data-lettres] .hero__lettre {
    animation: lettre-arrivee 0.5s ease backwards;
    animation-delay: calc(var(--rang, 0) * 0.045s);
  }

  @keyframes lettre-arrivee {
    from {
      opacity: 0;
      transform: translateY(0.35em);
    }
  }

  /* --- Ouverture du menu -------------------------------------------------
     Le menu arrive comme tout le reste du site : un pan de couleur qui monte
     depuis le bas. C'est le geste du volet entre deux pages et celui du pied de
     page qui se découvre — trois endroits, un seul mouvement.

     Deux temps, et non un seul comme avant : le panneau couvre d'abord l'écran,
     puis la navigation s'y pose ligne après ligne. Le contenu attend que le pan
     soit arrivé — posé plus tôt, il flotterait au-dessus d'un écran encore à
     moitié découvert.

     La courbe est franche au départ et se pose longuement : à mi-durée le
     panneau a déjà fait 87 % du chemin, ce qui laisse les lignes entrer sur un
     fond plein sans qu'on ait à attendre la fin.

     Le bouton rond et sa pilule gardent leurs animations d'origine, plus bas :
     ils sont posés au pixel sur ceux de l'en-tête et ne doivent pas bouger d'un
     cheveu — seul le disque se remplit et le signe pivote.

     Aucune animation ne porte l'état final : si elle ne joue pas — mouvement
     refusé, onglet en arrière-plan —, le menu est simplement là, entier. */
  html.js .menu[open]::before {
    animation: panneau-monte var(--duree-menu) cubic-bezier(0.16, 1, 0.3, 1);
  }

  html.js .menu[open]::backdrop {
    animation: voile-apparition 0.25s ease;
  }

  /* Les trois lignes se posent l'une après l'autre. Le retrait est écrit ligne
     par ligne plutôt que calculé : elles sont trois, elles le resteront, et un
     rang posé par le script serait du style en ligne pour trois valeurs. */
  html.js .menu[open] .menu__liste > li {
    animation: ligne-arrivee 0.3s ease backwards;
  }

  html.js .menu[open] .menu__liste > li:nth-child(1) {
    animation-delay: 0.26s;
  }

  html.js .menu[open] .menu__liste > li:nth-child(2) {
    animation-delay: 0.31s;
  }

  html.js .menu[open] .menu__liste > li:nth-child(3) {
    animation-delay: 0.36s;
  }

  /* Le pied ferme la marche, une fois les trois lignes en place. */
  html.js .menu[open] .menu__pied {
    animation: ligne-arrivee 0.3s ease 0.42s backwards;
  }

  html.js .menu[open] .bouton-pilule {
    animation: fond-apparition var(--duree-menu) ease;
  }

  html.js .menu[open] .icone-plus {
    animation: bascule-plus var(--duree-menu) ease;
  }

  /* Seul le disque se remplit — le bouton lui-même ne bouge ni ne s'efface. */
  html.js .menu[open] .bouton-rond {
    animation: disque-apparition var(--duree-menu) ease;
  }

  html.js .menu[data-fermeture] .bouton-rond {
    animation: disque-disparition var(--duree-menu) ease forwards;
  }

  /* La fermeture est d'un seul tenant, et plus rapide que l'ouverture : le
     contenu s'efface d'abord, le panneau se retire ensuite vers le haut. On ne
     remonte pas un escalier qu'on vient de descendre — personne n'attend une sortie mise en
     scène.
     C'est .menu__nav que main.js guette pour savoir quand refermer le <dialog> :
     son animation doit donc durer aussi longtemps que celle du panneau, d'où les
     paliers de contenu-disparition, qui l'efface en premier tiers puis le laisse
     là où il est. Changer l'un sans l'autre rouvrirait le filet de 1,2 s. */
  html.js .menu[data-fermeture]::before {
    animation: panneau-descend var(--duree-menu) cubic-bezier(0.7, 0, 0.84, 0) forwards;
  }

  html.js .menu[data-fermeture]::backdrop {
    animation: voile-disparition var(--duree-menu) ease forwards;
  }

  html.js .menu[data-fermeture] .menu__nav,
  html.js .menu[data-fermeture] .menu__pied {
    animation: contenu-disparition var(--duree-menu) ease forwards;
  }

  html.js .menu[data-fermeture] .bouton-pilule {
    animation: fond-disparition var(--duree-menu) ease forwards;
  }

  html.js .menu[data-fermeture] .icone-plus {
    animation: bascule-plus-retour var(--duree-menu) ease forwards;
  }

  @keyframes fond-apparition {
    from {
      opacity: 0;
    }
  }

  @keyframes fond-disparition {
    to {
      opacity: 0;
    }
  }

  @keyframes panneau-monte {
    from {
      clip-path: inset(0 0 100% 0);
    }
  }

  @keyframes panneau-descend {
    to {
      clip-path: inset(0 0 100% 0);
    }
  }

  @keyframes voile-apparition {
    from {
      opacity: 0;
    }
  }

  @keyframes voile-disparition {
    to {
      opacity: 0;
    }
  }

  @keyframes ligne-arrivee {
    from {
      opacity: 0;
      transform: translateY(-1.25rem);
    }
  }

  /* Trois paliers et non deux : le contenu doit être parti bien avant que le
     panneau n'ait fini de descendre, mais son animation doit durer autant que
     lui — c'est elle que le script guette pour refermer le <dialog>. */
  @keyframes contenu-disparition {
    0% {
      opacity: 1;
    }

    35% {
      opacity: 0;
      transform: translateY(-0.75rem);
    }

    100% {
      opacity: 0;
      transform: translateY(-0.75rem);
    }
  }

  @keyframes disque-apparition {
    from {
      background-color: transparent;
    }
  }

  @keyframes disque-disparition {
    to {
      background-color: transparent;
    }
  }

  @keyframes bascule-plus {
    from {
      transform: rotate(0deg);
    }
  }

  @keyframes bascule-plus-retour {
    to {
      transform: rotate(0deg);
    }
  }
}

/* Défilement bloqué pendant que le menu est ouvert. */
body.menu-ouvert {
  overflow: hidden;
}
