/**
 * Boussolio — animations.
 *
 * PRINCIPE DIRECTEUR
 * Aucune animation de ce fichier n'est pilotée par JavaScript. Tout repose sur
 * les animations natives liées au défilement (`animation-timeline`), gérées
 * par le compositeur du navigateur. Conséquences concrètes :
 *   — aucun écouteur `scroll`, donc aucun travail sur le fil principal ;
 *   — aucun `IntersectionObserver` ;
 *   — les animations restent fluides même pendant que la page charge.
 *
 * TROIS RÈGLES QUI NE SOUFFRENT AUCUNE EXCEPTION
 *
 * 1. Le contenu est VISIBLE PAR DÉFAUT.
 *    Les états de départ (opacité nulle, décalage) ne sont posés qu'à
 *    l'intérieur de `@supports (animation-timeline: view())`. Sur un
 *    navigateur qui ne connaît pas la fonctionnalité — environ 16 % du parc
 *    à la mi-2026 — la page s'affiche simplement sans animation. Elle ne
 *    s'affiche jamais vide.
 *
 * 2. On n'anime QUE `transform` et `opacity`.
 *    Ces deux propriétés ne déclenchent ni recalcul de mise en page ni
 *    repeinte. Animer `width`, `height`, `margin` ou `top` provoquerait des
 *    sauts de contenu — exactement ce qu'il faut éviter.
 *
 * 3. Rien ne bouge si le visiteur ne le veut pas.
 *    `prefers-reduced-motion: reduce` neutralise tout, en fin de fichier.
 *
 * @package Boussolio
 */

/* ==========================================================================
   1. Durées et courbes
   ========================================================================== */

:root {
	/* Une courbe unique pour les entrées : la cohérence vaut mieux que la
	   variété. Sortie franche, arrivée douce. */
	--bo-anim-ease: cubic-bezier(0.16, 1, 0.3, 1);
}

/* ==========================================================================
   2. Apparition progressive des sections
   ========================================================================== */

/*
 * `animation-timeline: view()` lie la progression de l'animation à la
 * traversée de l'élément dans la fenêtre — pas au temps qui passe.
 * `animation-range` la fait commencer quand l'élément entre par le bas et
 * finir quand il a parcouru 26 % de la hauteur de fenêtre : l'élément est
 * donc pleinement lisible bien avant d'atteindre le centre de l'écran.
 */
@supports (animation-timeline: view()) {

	.bo-apparait {
		opacity: 0;
		/* 14 px seulement. Un déplacement plus ample donne le tournis et
		   ressemble à un défaut de chargement. */
		transform: translateY(14px);
		animation: bo-monte both;
		animation-timeline: view();
		animation-range: entry 0% cover 26%;
	}

	/*
	 * Décalage en cascade dans une grille de cartes.
	 * Obtenu par `animation-range` et non par `animation-delay` : un délai
	 * temporel se désynchroniserait du défilement dès que le visiteur va vite.
	 */
	.bo-cascade > *:nth-child(1) { animation-range: entry 0%  cover 22%; }
	.bo-cascade > *:nth-child(2) { animation-range: entry 2%  cover 25%; }
	.bo-cascade > *:nth-child(3) { animation-range: entry 4%  cover 28%; }
	.bo-cascade > *:nth-child(4) { animation-range: entry 6%  cover 31%; }
	.bo-cascade > *:nth-child(5) { animation-range: entry 8%  cover 34%; }
	.bo-cascade > *:nth-child(n+6) { animation-range: entry 10% cover 37%; }

	.bo-cascade > * {
		opacity: 0;
		transform: translateY(16px);
		animation: bo-monte both;
		animation-timeline: view();
	}

	/* Variante latérale, pour les blocs de comparaison. */
	.bo-apparait--gauche {
		opacity: 0;
		transform: translateX(-18px);
		animation: bo-vient-gauche both;
		animation-timeline: view();
		animation-range: entry 0% cover 28%;
	}

	.bo-apparait--droite {
		opacity: 0;
		transform: translateX(18px);
		animation: bo-vient-droite both;
		animation-timeline: view();
		animation-range: entry 0% cover 28%;
	}

	/* Variante d'échelle, réservée aux médias et aux grandes cartes. */
	.bo-apparait--echelle {
		opacity: 0;
		transform: scale(0.972);
		animation: bo-echelle both;
		animation-timeline: view();
		animation-range: entry 0% cover 30%;
	}
}

@keyframes bo-monte {
	to { opacity: 1; transform: translateY(0); }
}

@keyframes bo-vient-gauche {
	to { opacity: 1; transform: translateX(0); }
}

@keyframes bo-vient-droite {
	to { opacity: 1; transform: translateX(0); }
}

@keyframes bo-echelle {
	to { opacity: 1; transform: scale(1); }
}

/* ==========================================================================
   3. Progression de lecture
   ========================================================================== */

/*
 * `scroll()` suit le défilement du document entier. La barre se remplit à
 * mesure que l'on descend dans l'article.
 *
 * Sans prise en charge, la règle est simplement ignorée et la barre reste à
 * `scaleX(0)` — invisible, mais elle n'occupe aucune place et ne gêne rien.
 */
@supports (animation-timeline: scroll()) {
	.bo-progression {
		animation: bo-remplit linear both;
		animation-timeline: scroll(root block);
	}
}

@keyframes bo-remplit {
	from { transform: scaleX(0); }
	to   { transform: scaleX(1); }
}

/* ==========================================================================
   4. Fond en mouvement léger
   ========================================================================== */

/*
 * Parallaxe très contenue sur le décor de la section d'accueil.
 * Amplitude : 40 px sur toute la hauteur de la section. C'est volontairement
 * peu — assez pour donner de la profondeur, trop peu pour distraire.
 *
 * `pointer-events: none` : le décor ne doit jamais intercepter un clic.
 */
.bo-decor {
	position: absolute;
	inset: 0;
	z-index: 0;
	pointer-events: none;
	overflow: hidden;
	border-radius: inherit;
}

.bo-decor > * {
	position: absolute;
	will-change: transform;
}

@supports (animation-timeline: view()) {
	.bo-decor__couche {
		animation: bo-parallaxe linear both;
		animation-timeline: view();
		animation-range: cover 0% cover 100%;
	}
}

@keyframes bo-parallaxe {
	from { transform: translate3d(0, -20px, 0); }
	to   { transform: translate3d(0,  20px, 0); }
}

/* ==========================================================================
   5. Survols
   ========================================================================== */

/*
 * Une carte se soulève de 3 px. C'est tout.
 * `transform` et `box-shadow` uniquement : la carte ne change pas de taille,
 * donc rien autour d'elle ne se déplace.
 */
.bo-carte {
	transition: transform var(--bo-dur) var(--bo-ease-out),
	            box-shadow var(--bo-dur) var(--bo-ease-out),
	            border-color var(--bo-dur-fast) var(--bo-ease);
}

@media (hover: hover) and (pointer: fine) {
	.bo-carte:hover {
		transform: translateY(-3px);
		box-shadow: var(--bo-shadow-3);
		border-color: var(--bo-border-strong);
	}
}

/* Le focus clavier produit le même effet que le survol : parité d'expérience. */
.bo-carte:focus-within {
	transform: translateY(-3px);
	box-shadow: var(--bo-shadow-3);
	border-color: var(--bo-border-strong);
}

/*
 * Flèche des liens « Voir le comparatif → ».
 * Le glissement de 3 px se fait sur un pseudo-élément en `inline-block`,
 * jamais sur le texte lui-même.
 */
.bo-lien-fleche {
	display: inline-flex;
	align-items: center;
	gap: 0.4em;
	font-weight: 700;
	text-decoration: none;
}

.bo-lien-fleche::after {
	content: "→";
	display: inline-block;
	transition: transform var(--bo-dur) var(--bo-ease-out);
}

.bo-lien-fleche:hover::after,
.bo-lien-fleche:focus-visible::after {
	transform: translateX(3px);
}

/* ==========================================================================
   6. Ouverture des comparaisons
   ========================================================================== */

/*
 * Ouverture fluide d'un `<details>`.
 *
 * `interpolate-size: allow-keywords` permet d'animer vers `height: auto`, ce
 * qui était impossible jusqu'à récemment. Le tout est enfermé dans un
 * `@supports` : là où ce n'est pas géré, le `<details>` s'ouvre d'un coup —
 * comportement natif, parfaitement acceptable.
 */
@supports (interpolate-size: allow-keywords) {
	:root {
		interpolate-size: allow-keywords;
	}

	.bo-depliant::details-content {
		height: 0;
		overflow: hidden;
		opacity: 0;
		transition: height var(--bo-dur-slow) var(--bo-ease-out),
		            opacity var(--bo-dur) var(--bo-ease),
		            content-visibility var(--bo-dur-slow) allow-discrete;
	}

	.bo-depliant[open]::details-content {
		height: auto;
		opacity: 1;
	}
}

/* Le chevron pivote — sur le marqueur, jamais sur le texte du résumé. */
.bo-depliant__resume::marker,
.bo-depliant__resume::-webkit-details-marker {
	content: "";
	display: none;
}

.bo-depliant__chevron {
	transition: transform var(--bo-dur) var(--bo-ease-out);
	flex: 0 0 auto;
}

.bo-depliant[open] .bo-depliant__chevron {
	transform: rotate(180deg);
}

/* ==========================================================================
   7. Chiffres animés
   ========================================================================== */

/*
 * Compteur animé sans JavaScript, avec `@property`.
 * Enregistrer la variable comme un entier permet au navigateur de
 * l'interpoler ; `counter()` la rend visible.
 *
 * `aria-hidden` est posé sur l'élément animé et la vraie valeur est donnée
 * en texte à côté : un lecteur d'écran annonce le chiffre final, jamais le
 * défilement des dizaines.
 */
@property --bo-compteur {
	syntax: "<integer>";
	initial-value: 0;
	inherits: false;
}

@supports (animation-timeline: view()) and (syntax: "<integer>") {
	.bo-compteur {
		counter-reset: bo-nombre var(--bo-compteur);
		animation: bo-compte linear both;
		animation-timeline: view();
		animation-range: entry 10% cover 34%;
	}

	.bo-compteur::after {
		content: counter(bo-nombre);
	}
}

@keyframes bo-compte {
	to { --bo-compteur: var(--bo-cible, 100); }
}

/* ==========================================================================
   8. Transitions entre les pages
   ========================================================================== */

/*
 * Transitions de vue entre deux pages du site.
 * Purement additif : un navigateur qui ne les gère pas fait une navigation
 * classique, sans dégradation.
 */
@view-transition {
	navigation: auto;
}

::view-transition-old(root) {
	animation: bo-vt-sortie 120ms var(--bo-ease) both;
}

::view-transition-new(root) {
	animation: bo-vt-entree 200ms var(--bo-ease-out) both;
}

@keyframes bo-vt-sortie {
	to { opacity: 0; }
}

@keyframes bo-vt-entree {
	from { opacity: 0; transform: translateY(6px); }
	to   { opacity: 1; transform: translateY(0); }
}

/* Le panneau de navigation ne doit pas clignoter d'une page à l'autre. */
.bo-rail--left {
	view-transition-name: bo-rail-gauche;
}

.bo-barre-basse {
	view-transition-name: bo-barre-basse;
}

/* ==========================================================================
   10. Bascule de thème — suspension des transitions
   ==========================================================================

   Défaut constaté à la mesure, et corrigé ici.

   Quand une variable CSS change sur `:root`, les propriétés qui en dérivent
   changent aussi. Mais si l'une d'elles porte une `transition`, Chrome peut
   laisser cette transition FIGÉE : la propriété reste bloquée sur son
   ancienne valeur, indéfiniment. Mesuré sur les boutons de la barre
   inférieure — après passage en thème clair, leur couleur restait celle du
   thème sombre, même trois secondes plus tard, et ne se corrigeait qu'en
   supprimant la transition.

   Le remède est de suspendre toutes les transitions le temps de la bascule,
   puis de les rétablir. L'attribut est posé et retiré par app.js.

   Effet visible : le changement de thème est instantané au lieu d'être
   fondu — ce qui est de toute façon préférable, un fondu de toute la page
   étant plus désagréable qu'élégant.
   ========================================================================== */

:root[data-bo-transition="off"],
:root[data-bo-transition="off"] *,
:root[data-bo-transition="off"] *::before,
:root[data-bo-transition="off"] *::after {
	transition: none !important;
}

/* ==========================================================================
   9. Mouvement réduit — neutralisation complète
   ========================================================================== */

/*
 * Quand le système demande moins de mouvement, on ne se contente pas de
 * raccourcir : on supprime.
 *
 * Point capital : les éléments doivent redevenir PLEINEMENT VISIBLES.
 * Se contenter de `animation: none` laisserait `.bo-apparait` figé à
 * `opacity: 0` — la page serait vide. D'où les remises à zéro explicites.
 */
@media (prefers-reduced-motion: reduce) {

	*,
	*::before,
	*::after {
		animation-duration: 0.001ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.001ms !important;
		scroll-behavior: auto !important;
	}

	.bo-apparait,
	.bo-apparait--gauche,
	.bo-apparait--droite,
	.bo-apparait--echelle,
	.bo-cascade > * {
		opacity: 1 !important;
		transform: none !important;
		animation: none !important;
	}

	.bo-decor__couche {
		animation: none !important;
		transform: none !important;
	}

	.bo-carte:hover,
	.bo-carte:focus-within {
		transform: none !important;
	}

	.bo-lien-fleche:hover::after,
	.bo-lien-fleche:focus-visible::after {
		transform: none !important;
	}

	/* La barre de progression garde son sens : elle reste, sans transition. */
	.bo-progression {
		transition: none !important;
	}

	/* Aucune transition entre les pages. */
	@view-transition {
		navigation: none;
	}

	::view-transition-old(root),
	::view-transition-new(root) {
		animation: none !important;
	}
}
