/* ─────────────────────────────────────────────────────────────────────────
   AYNIRA - Mouvement

   Un seul module porte tout le mouvement du site. Les durées et la courbe sont
   déclarées avec le reste du contrat dans 00-foundation.css ; ici ne vivent que
   les règles qui les consomment.

   Trois principes, tous déduits de la DA existante :

   1. Le mouvement suit la STRUCTURE, jamais la géographie de l'écran. Un retard
      qui dépend de la position produit une vague qui traverse la fenêtre ; c'est
      l'effet qui a fait retirer le balayage jour / nuit le 20.08.2026. Ici le
      seul retard du site est celui d'un sous-bloc sur son bloc — la hiérarchie
      à deux niveaux de la DA, appliquée au temps. Deux temps, pas davantage.

   2. Aucun mouvement libre. Une translation est une fraction de --bg-grid-size
      ou un multiple de --ui-frame-space, jamais une valeur libre, jamais plus
      d'un quart de cellule. Pas de scale sur un bloc encadré : un cadre de 1px
      mis à l'échelle est un cadre flou, et le site a un contrat de netteté.

   3. Aucune couleur nouvelle. Une animation fait passer un élément entre deux
      états déjà écrits dans la DA ; elle n'en invente pas un troisième. Ni
      lueur, ni ombre animée, ni flou, ni trame allumée.

   L'arrivée des blocs est purement CSS : elle démarre à la première image, sans
   attendre un script, et elle est terminée avant qu'un défilement soit possible.
   Le pilote assets/js/main/motion.js ne s'occupe que de ce que le CSS ne peut
   pas faire : dégrader sur appareil lent, et suspendre les canvas hors écran.
   ───────────────────────────────────────────────────────────────────────── */

/* ── Mouvement réduit ────────────────────────────────────────────────────
   Écrit une seule fois pour tout le site. 1ms plutôt que `none` : les
   gestionnaires transitionend / animationend continuent de se déclencher, donc
   aucune logique JS ne reste bloquée en attendant une fin qui ne vient pas. */
@media (prefers-reduced-motion: reduce) {
  html[data-motion] *,
  html[data-motion] *::before,
  html[data-motion] *::after {
    animation-delay: 0ms !important;
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-delay: 0ms !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
}
/* Échappatoire de débogage et repli des appareils lents : le pilote pose
   data-motion="none" et tout le module se débranche d'un coup. */
html[data-motion="none"] *,
html[data-motion="none"] *::before,
html[data-motion="none"] *::after {
  animation-delay: 0ms !important;
  animation-duration: 1ms !important;
  transition-delay: 0ms !important;
  transition-duration: 1ms !important;
}

/* ═══ 1. La navigation entre pages ═════════════════════════════════════ */

/* Aucun bloc n'a d'animation d'arrivée, et c'est délibéré. Une version de ce
   module faisait se déplier les panneaux au chargement de chaque page ; l'effet
   a été retiré entièrement le 20.08.2026, comme le balayage jour / nuit avant
   lui, et pour la même raison : ce n'est pas un réglage qui n'allait pas, c'est
   le geste. Ne pas le réintroduire sous une autre forme — ni fondu de bloc, ni
   découpage, ni décalage à l'ouverture d'une page. Le contrôle statique refuse
   toute règle qui reviendrait à en poser un.

   Il reste donc ici, pour l'ouverture d'une page, le seul mouvement que le
   navigateur produit lui-même d'un document à l'autre. */

/* Navigation entre deux documents : le navigateur croise les deux pages
   lui-même là où il sait le faire. Rien à piloter. */
@view-transition {
  navigation: auto;
}
/* Les pseudo-éléments de transition ne sont pas des descendants de <html> : la
   règle générale de mouvement réduit, écrite en `html[data-motion] *`, ne les
   atteint pas. Sans ces deux blocs, une personne ayant demandé moins de
   mouvement recevrait quand même le fondu entre pages. */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}
html[data-motion="none"]::view-transition-group(*),
html[data-motion="none"]::view-transition-old(*),
html[data-motion="none"]::view-transition-new(*) {
  animation: none !important;
}
/* Le rail de gauche est le même d'une vue à l'autre : nommé, il reste
   physiquement immobile pendant que le contenu se remplace, au lieu de
   disparaître et de revenir.

   Un view-transition-name doit être UNIQUE dans le document, sans quoi la
   transition entière est abandonnée. Les deux sélecteurs ci-dessous le sont par
   construction : le rail du profil est rendu dans un if/else (identité ou
   filtres, jamais les deux), et la Librairie n'a qu'une barre latérale. Ne pas
   élargir ces sélecteurs sans revérifier cette unicité. */
#profile-page > .profile-context-panel {
  view-transition-name: aynira-profile-rail;
}
#library-page > .library-sidebar {
  view-transition-name: aynira-library-rail;
}

/* ═══ 2. Les micro-états ════════════════════════════════════════════════ */

/* 98 états :hover pour 17 transitions avant ce module. Aucune couleur n'est
   modifiée ici : on ne fait que rendre continu ce qui est déjà écrit.

   background-image est indispensable et non décoratif : la quasi-totalité des
   états actifs du site passe par un dégradé (--ui-grad, --profile-grad,
   --stat-grad, --neutral-grad, --ui-side-grad), pas par une couleur plate. Ils
   s'interpolent parce qu'ils ont tous la même structure — linear-gradient(90deg)
   à trois arrêts. Sans cette ligne, les états les plus visibles du site
   continueraient de claquer.

   Pas de `transition: all` : l'arrivée anime clip-path et transform, un `all`
   les capturerait et doublerait chaque entrée de bloc. */
html[data-motion] :is(
  button,
  summary,
  input,
  select,
  textarea,
  .nav-primary-link,
  .nav-auth a,
  .nav-theme-label,
  .library-card,
  .card-thumb,
  .card-meta,
  .library-source-row,
  .library-mode-toggle a,
  .library-add-button,
  .refs-category-list a,
  .profile-collection-link,
  .profile-title-item,
  .admin-switcher a,
  .edit-switcher a,
  .detail-edit-button,
  .detail-back,
  .atlas-mode-button,
  .viz-action-button,
  .about-support-heart,
  .auth-secondary-link,
  .media-lightbox-nav,
  .media-lightbox-close
) {
  transition:
    background-color var(--motion-state) var(--motion-ease),
    background-image var(--motion-state) var(--motion-ease),
    border-color var(--motion-state) var(--motion-ease),
    color var(--motion-state) var(--motion-ease),
    opacity var(--motion-state) var(--motion-ease);
}

/* L'enfoncement est un accusé de réception, pas une animation : il est
   instantané. Seul le relâchement est amorti. */
html[data-motion] :is(button, summary, .library-card, .profile-collection-link):active {
  transition-duration: 0ms;
}

/* Une carte de Librairie a trois cadres — extérieur, image, bandeau — que la DA
   fait changer ensemble. Ils doivent aussi changer ensemble dans le temps. */
html[data-motion] .library-card,
html[data-motion] .library-card .card-thumb,
html[data-motion] .library-card .card-meta {
  transition-duration: var(--motion-state);
}

/* Un interdit se lit comme un état, pas comme un bug. */
html[data-motion] :is(button, input, select, textarea):disabled,
html[data-motion] button[aria-disabled="true"] {
  transition-duration: var(--motion-state);
}

/* Le focus n'est jamais amorti : au clavier, le repère doit être là avant que
   l'œil arrive, pas après. */
html[data-motion] :focus-visible {
  transition-property: background-color, background-image, border-color, color;
}

/* Le bouton d'enregistrement du profil bascule propre -> modifié par une classe
   posée en JS (profile-display.js). C'est un changement d'état comme un autre. */
html[data-motion] .detail-edit-button.profile-save-button {
  transition:
    background-color var(--motion-state) var(--motion-ease),
    background-image var(--motion-state) var(--motion-ease),
    border-color var(--motion-state) var(--motion-ease),
    color var(--motion-state) var(--motion-ease);
}

/* ═══ 3. Les surfaces ═══════════════════════════════════════════════════ */

/* Les <dialog> natifs s'animent sans une ligne de JS : allow-discrete rend
   `display` et `overlay` interpolables, @starting-style donne l'état de départ
   de l'ouverture. Aucun transform sur la carte — elle est centrée sur une
   géométrie fixe, la déplacer romprait l'alignement au pixel. */
html[data-motion] :is(.ui-compact-dialog, .site-tour) {
  opacity: 1;
  transition:
    opacity var(--motion-surface) var(--motion-ease),
    overlay var(--motion-surface) var(--motion-ease) allow-discrete,
    display var(--motion-surface) var(--motion-ease) allow-discrete;
}
html[data-motion] :is(.ui-compact-dialog, .site-tour):not([open]) {
  opacity: 0;
}
@starting-style {
  html[data-motion] :is(.ui-compact-dialog, .site-tour)[open] {
    opacity: 0;
  }
}
/* Le voile monte au lieu d'apparaître : c'est lui qui porte le passage à
   l'avant-plan, la carte ne fait que le suivre. */
html[data-motion] :is(.ui-compact-dialog, .site-tour)::backdrop {
  opacity: 1;
  transition:
    opacity var(--motion-surface) var(--motion-ease),
    overlay var(--motion-surface) var(--motion-ease) allow-discrete,
    display var(--motion-surface) var(--motion-ease) allow-discrete;
}
html[data-motion] :is(.ui-compact-dialog, .site-tour):not([open])::backdrop {
  opacity: 0;
}
@starting-style {
  html[data-motion] :is(.ui-compact-dialog, .site-tour)[open]::backdrop {
    opacity: 0;
  }
}

/* Les surfaces créées en JS et affichées par `hidden` : `hidden` n'est pas
   animable, le pilote pose donc `is-open` à l'image suivante et le retire avant
   de reposer `hidden` sur transitionend. Le contenu arrive plus vite que son
   support — c'est ce qui donne l'impression que la surface était déjà là. */
html[data-motion] :is(.media-lightbox, .atlas-rameau-help) {
  opacity: 0;
  transition: opacity var(--motion-surface) var(--motion-ease);
}
html[data-motion] :is(.media-lightbox, .atlas-rameau-help).is-open {
  opacity: 1;
}
html[data-motion] .media-lightbox .media-lightbox-figure {
  opacity: 0;
  transform: translate3d(0, calc(var(--ui-frame-space) * 2), 0);
  transition:
    opacity var(--motion-state) var(--motion-ease),
    transform var(--motion-state) var(--motion-ease);
}
html[data-motion] .media-lightbox.is-open .media-lightbox-figure {
  opacity: 1;
  transform: translate3d(0, 0, 0);
}

/* Les tiroirs dépliables. interpolate-size débloque l'interpolation vers
   `auto` ; là où il n'est pas compris, l'ouverture reste sèche et rien ne
   casse. Le marqueur suit la même durée que n'importe quel autre état. */
html[data-motion] {
  interpolate-size: allow-keywords;
}
html[data-motion] .profile-collection > summary::marker,
html[data-motion] .profile-collection > summary::-webkit-details-marker {
  transition: transform var(--motion-state) var(--motion-ease);
}
html[data-motion] details::details-content {
  block-size: 0;
  overflow: hidden;
  transition:
    block-size var(--motion-surface) var(--motion-ease),
    content-visibility var(--motion-surface) var(--motion-ease) allow-discrete;
}
html[data-motion] details[open]::details-content {
  block-size: auto;
}

/* ═══ 4. Les canvas ═════════════════════════════════════════════════════ */

/* Le L-système pousse depuis sa racine quand sa vignette entre dans l'écran.
   Avant que l'observateur ait pu se prononcer, le rendu a déjà eu lieu une fois
   avec l'arbre entier : sans ce voile, la pousse serait précédée d'un éclair de
   l'arbre fini. C'est la seule raison d'être de cette règle — un voile qui ne
   masque rien n'est pas un effet, c'est une latence.

   Les visualisations des statistiques et de la facturation n'en ont donc pas :
   elles se dessinent une fois, correctement, et sont là. */
html[data-motion="full"] .work-lsystem-canvas {
  opacity: 0;
  transition: opacity var(--motion-state) var(--motion-ease);
}
html[data-motion="full"] .work-lsystem-canvas.is-drawn {
  opacity: 1;
}

/* La bande du continuum d'offres coule d'une offre à l'autre au lieu de sauter
   d'un bandeau au suivant. C'est la seule chose qui rende les quatre cartes
   lisibles comme un seul objet plutôt que comme quatre. */
html[data-motion] .billing-plan-card .billing-plan-name {
  transition:
    background-image var(--motion-long) var(--motion-ease),
    border-color var(--motion-state) var(--motion-ease);
}
