/*
 * Effets et animations.
 *
 * tokens.css et les feuilles de composants décrivent la charte en variables ;
 * ici vivent les seules choses qu'une variable ne sait pas dire : un halo
 * flouté, un glow, une entrée décalée, un en-tête qui se rétracte, une
 * révélation au défilement.
 *
 * Deux garde-fous. Tout ce qui bouge est sous
 * `prefers-reduced-motion: no-preference` : qui a demandé moins d'animations
 * obtient la page immobile, sans rien perdre du contenu. Et rien n'est masqué
 * sans qu'un script soit là pour le révéler — `@media (scripting: enabled)`
 * d'abord, puis une animation de secours si main.js ne répond pas.
 *
 * Seules la révélation et la rétraction de l'en-tête demandent du JavaScript,
 * et main.js s'y limite à poser deux marqueurs : `is-revealed` et
 * `is-scrolled`.
 */

/* --header-h est animé plus bas. Pour qu'un navigateur sache interpoler une
   variable, il faut lui en déclarer le type ; sans ça, elle basculerait d'une
   valeur à l'autre d'un coup. La valeur initiale reprend celle de tokens.css. */
@property --header-h {
	syntax: "<length>";
	inherits: true;
	initial-value: 72px;
}

@keyframes kiaros-enter {
	from {
		opacity: 0;
		transform: translateY(22px);
	}

	to {
		opacity: 1;
		transform: none;
	}
}

@keyframes kiaros-failsafe {
	to {
		opacity: 1;
		transform: none;
	}
}

@keyframes kiaros-breathe {
	from {
		opacity: 0.7;
		transform: scale(1);
	}

	to {
		opacity: 1;
		transform: scale(1.12);
	}
}

@keyframes kiaros-pulse {
	from {
		box-shadow: 0 0 0 0 color-mix(in srgb, var(--c-accent) 27.5%, transparent);
	}

	to {
		box-shadow: 0 0 0 0.55rem transparent;
	}
}

/*
 * Signature de la charte : un bloc bleu encre en ouverture de page — le héros
 * de l'accueil, et l'en-tête de toutes les pages internes. On rebascule la
 * palette locale sur les jetons de bande, exactement comme le fait
 * .section--alt. Sur l'accueil, la section qui suit le héros étant elle-même
 * alternée, les deux blocs verts se rejoignent.
 */
.hero,
.page-hero {
	background: var(--c-alt-bg);
	color: var(--c-alt-text);

	/* Ce que « le fond de la page » veut dire à l'intérieur d'une bande.
	   Par défaut, le fond général — les huit premières chartes ne bougent
	   pas d'un pixel. Le thème principal y met la bande : sans ça, les
	   composants qui s'y réfèrent (lignes de la console du héros, champs
	   de formulaire) restent ivoire au milieu de la bande. */
	--c-bg: var(--c-alt-page);
	--c-text: var(--c-alt-text);
	--c-text-soft: var(--c-alt-text-soft);
	--c-text-muted: var(--c-alt-text-muted);
	--c-surface: var(--c-alt-surface);
	--c-surface-2: var(--c-alt-surface-2);
	--c-border: var(--c-alt-border);
	--c-border-strong: var(--c-alt-border-strong);
	--c-accent: var(--c-alt-accent);
	--c-accent-hover: var(--c-alt-accent-hover);
	--c-accent-text: var(--c-alt-accent-text);
	--c-accent-soft: var(--c-alt-accent-soft);
	--c-cta: var(--c-alt-cta);
	--c-cta-hover: var(--c-alt-cta-hover);
	--c-cta-text: var(--c-alt-cta-text);
}

/* 1. Le halo du héros : mêmes radiales que partout, mais floutées. On passe
   d'un dégradé net à deux bulles de lumière. */
.hero::before {
	filter: blur(60px);
}

/* 2. Le fragment accentué du titre s'allume. Trois ombres de portée
   croissante : c'est ce qui fait la différence entre « vert » et « néon ». */
.hero__title em {
	text-shadow:
		0 0 12px color-mix(in srgb, var(--c-accent) 25%, transparent),
		0 0 36px color-mix(in srgb, var(--c-accent) 15%, transparent),
		0 0 72px color-mix(in srgb, var(--c-accent) 7.5%, transparent);
}

/* 3. Les cartes prennent un reflet diagonal et une bordure qui se teinte au
   survol. La nuance suit le texte : sombre sur ivoire, claire sur la bande. */
:is(.card, .plan) {
	background-image: linear-gradient(155deg, color-mix(in srgb, var(--c-text) 3%, transparent), transparent 62%);
}

.card:hover {
	border-color: color-mix(in srgb, var(--c-cta) 35%, var(--c-border));
	box-shadow: var(--shadow-lg);
}

/* 4. Le bouton principal se soulève dans un halo de sa propre couleur. */
.button--primary:hover {
	box-shadow:
		0 10px 34px color-mix(in srgb, var(--c-cta) 22.5%, transparent),
		inset 0 1px 0 rgba(255, 255, 255, 0.35);
}

@media screen and (prefers-reduced-motion: no-preference) {
	.card:hover {
		transform: translateY(-4px);
	}

	.button--primary:hover {
		transform: translateY(-2px) scale(1.02);
	}

	/*
	 * 5. Entrée du héros. On anime les blocs — accroche, titre, texte,
	 * boutons, preuves — plutôt que les mots un par un : le balisage doit
	 * rester identique d'une charte à l'autre. Le décalage et la courbe font
	 * l'essentiel de l'effet.
	 */
	:is(.hero__inner > :first-child > *, .hero__panel) {
		animation: kiaros-enter 720ms cubic-bezier(0.22, 1, 0.36, 1) both;
	}

	.hero__inner > :first-child > :nth-child(2) {
		animation-delay: 90ms;
	}

	.hero__inner > :first-child > :nth-child(3) {
		animation-delay: 180ms;
	}

	.hero__inner > :first-child > :nth-child(4) {
		animation-delay: 270ms;
	}

	.hero__inner > :first-child > :nth-child(n + 5) {
		animation-delay: 360ms;
	}

	/* Le raccourci `animation` ci-dessus remet le délai à zéro et sa
	   spécificité est celle du :is() le plus fort : il faut donc viser le
	   panneau par son parent pour que ce décalage passe devant. */
	.hero__inner > .hero__panel {
		animation-delay: 220ms;
	}

	/* 6. Les deux bulles de lumière respirent, très lentement. */
	.hero::before {
		animation: kiaros-breathe 9s ease-in-out infinite alternate;
	}

	/* 7. La pastille verte de la console pulse : le panneau a l'air vivant. */
	.hero__panel-dot:first-child {
		animation: kiaros-pulse 2.6s ease-out infinite;
	}

	/*
	 * 8. L'en-tête se rétracte dès qu'on quitte le haut de la page. Le
	 * marqueur `is-scrolled` est posé par main.js. C'est --header-h qui est
	 * animé, pas une hauteur en dur : le décalage des ancres, le panneau de
	 * navigation mobile et les colonnes collantes suivent tout seuls.
	 */
	.site-header {
		--header-h: 86px;

		transition:
			--header-h 400ms cubic-bezier(0.22, 1, 0.36, 1),
			box-shadow 400ms cubic-bezier(0.22, 1, 0.36, 1);
	}

	.is-scrolled .site-header {
		--header-h: 60px;

		box-shadow: var(--shadow-md);
	}

	/*
	 * 9. Révélation au défilement.
	 *
	 * Le déclenchement est confié à main.js, qui pose `is-revealed` sur un bloc
	 * dès qu'il entre dans la fenêtre. La première version s'appuyait sur
	 * `animation-timeline: view()`, sans aucun JavaScript — mais une minuterie
	 * de défilement suppose que le document lui-même défile : dans une
	 * visionneuse intégrée, ou une iframe dimensionnée à son contenu, elle
	 * n'avance jamais et plus rien ne se révélait. Un déclenchement observé
	 * marche dans tous ces cas, et l'animation va jusqu'au bout une fois
	 * lancée au lieu de suivre le doigt.
	 *
	 * `scripting: enabled` protège le contenu : sans JavaScript, rien n'est
	 * masqué. C'est la seule façon honnête de cacher quelque chose au
	 * chargement.
	 *
	 * On ne révèle que les blocs terminaux d'une section, jamais leurs
	 * conteneurs : viser `.grid` plutôt que `.card` animerait la grille et ses
	 * cartes l'une dans l'autre. D'où le `:not(…)` sur les colonnes. `.steps` y
	 * est nommée à part : `:has()` ne regarde que les descendants de l'élément
	 * testé, un sélecteur qui se référence lui-même n'y répond pas.
	 */
	@media (scripting: enabled) {
		:is(
			.section__head,
			.split > *:not(:where(.steps, :has(.card, .plan, .stat, .benefit, .faq__item))),
			.card,
			.plan,
			.stat,
			.stat__value,
			.benefit,
			.steps li,
			.marquee li,
			.faq__item,
			.quote,
			.compare,
			.callout,
			.notice,
			.editor-note,
			.aside-card,
			.tool-embed,
			.contact-panel,
			.post-grid > *,
			.pole-grid > *,
			.cta__inner
		) {
			transition:
				opacity 700ms cubic-bezier(0.22, 1, 0.36, 1),
				transform 700ms cubic-bezier(0.22, 1, 0.36, 1);
		}

		/*
		 * L'état masqué est conditionné à l'absence du marqueur, plutôt que
		 * révélé par une règle concurrente. C'est délibéré : une règle
		 * « .is-revealed { opacity: 1 } » doit peser plus lourd que celle qui
		 * masque, et la liste ci-dessus contient des sélecteurs de type
		 * (`.steps li`) qui la font gagner d'un cheveu. Le bloc restait alors
		 * invisible, marqueur posé. Ici, la règle cesse simplement de
		 * s'appliquer : plus rien à arbitrer.
		 */
		:is(
			.section__head,
			.split > *:not(:where(.steps, :has(.card, .plan, .stat, .benefit, .faq__item))),
			.card,
			.plan,
			.stat,
			.benefit,
			.steps li,
			.faq__item,
			.notice,
			.editor-note,
			.aside-card,
			.post-grid > *,
			.pole-grid > *,
			.cta__inner
		):not(.is-revealed) {
			opacity: 0;
			transform: translateY(26px);
			/* Filet de sécurité, voir --fx-failsafe plus bas. */
			animation: kiaros-failsafe 400ms ease-out var(--fx-failsafe, 800ms) forwards;
		}

		/*
		 * Sauf en haut de la première section, qui n'est jamais masquée.
		 *
		 * Une animation d'apparition au défilement n'a aucune raison de cacher ce
		 * qui est déjà à l'écran, et elle y coûte cher : tant qu'un bloc est à
		 * `opacity: 0`, il n'est pas peint, et le navigateur ne le retient pas
		 * comme candidat au Largest Contentful Paint. Comme main.js est chargé en
		 * `defer`, la mesure attendait la fin de l'analyse du HTML puis
		 * l'exécution du module.
		 *
		 * Mesuré à 1280 × 900 : l'accueil n'était pas concerné, son héros remplit
		 * le premier écran et n'a jamais été masqué. Mais /services/ avait quatre
		 * blocs masqués dans le premier écran, dont des cartes à 781 px, et /blog/
		 * six cartes dès 465 px.
		 *
		 * L'exception s'arrête à l'intitulé de section et aux trois premiers
		 * enfants d'une grille — soit la première rangée, la seule qui puisse
		 * porter le LCP. Au-delà, l'effet reprend : dégager la section entière
		 * l'aurait supprimé sur /blog/, dont toute la grille tient dans une seule
		 * section.
		 */
		.site-main > section:first-of-type .section__head,
		.site-main > section:first-of-type
			:is(.grid, .post-grid, .pole-grid, .pricing) > :nth-child(-n + 3) {
			opacity: 1;
			transform: none;
			animation: none;
		}

		/* Les blocs encadrés s'ouvrent en plus de monter, la bande de secteurs
		   glisse de côté, le chiffre d'une statistique se pose après son
		   cadre : trois mouvements valent mieux qu'un seul répété. */
		:is(.quote, .callout, .compare, .tool-embed, .contact-panel):not(.is-revealed) {
			opacity: 0;
			transform: translateY(26px) scale(0.98);
		}

		.marquee li:not(.is-revealed) {
			opacity: 0;
			transform: translateX(-18px);
		}

		.stat__value:not(.is-revealed) {
			opacity: 0;
			transform: translateY(14px);
		}

		.stat__value {
			transition-delay: 160ms;
		}

		/* Décalage par rang. Toutes les rangées d'une grille démarrent au même
		   moment — c'est la grille qui déclenche, pas chaque carte — mais
		   chacune part avec un léger retard sur sa voisine : la cascade se lit
		   en diagonale au lieu de se remplir ligne après ligne. */
		:is(.card, .plan, .stat, .benefit, .steps li, .marquee li, .faq__item):nth-child(2) {
			transition-delay: 80ms;
		}

		:is(.card, .plan, .stat, .benefit, .steps li, .marquee li, .faq__item):nth-child(3) {
			transition-delay: 160ms;
		}

		:is(.card, .plan, .stat, .benefit, .steps li, .marquee li, .faq__item):nth-child(4) {
			transition-delay: 240ms;
		}

		:is(.card, .plan, .stat, .benefit, .steps li, .marquee li, .faq__item):nth-child(5) {
			transition-delay: 320ms;
		}

		:is(.card, .plan, .stat, .benefit, .steps li, .marquee li, .faq__item):nth-child(6) {
			transition-delay: 400ms;
		}

		:is(.card, .plan, .stat, .benefit, .steps li, .marquee li, .faq__item):nth-child(n + 7) {
			transition-delay: 460ms;
		}
	}

	/*
	 * Si main.js ne tourne pas — erreur de script, fichier absent, réseau
	 * coupé en cours de chargement — personne ne vient poser les marqueurs et
	 * la page resterait vide. `scripting: enabled` ne couvre que le cas où
	 * JavaScript est désactivé, pas celui où il échoue.
	 *
	 * Le module signale donc sa présence en posant data-kiaros-reveal sur la
	 * balise html. Sans ce signal, l'animation de secours ci-dessus rend tout
	 * visible au bout de 800 ms ; avec lui, elle est repoussée si loin qu'elle
	 * ne se déclenche jamais, et c'est l'observateur qui décide.
	 *
	 * Ce délai était de trois secondes. C'était trois secondes de sections
	 * blanches quand le script échoue — bien plus long que le temps qu'un
	 * visiteur accorde à une page qui semble cassée. Le prix de 800 ms est
	 * qu'un appareil très lent peut révéler d'un coup des blocs encore hors
	 * de l'écran, et perdre l'effet ; du contenu visible sans effet vaut mieux
	 * qu'un effet sans contenu.
	 */
	[data-kiaros-reveal] {
		--fx-failsafe: 100000s;
	}

	/*
	 * 10. La FAQ s'ouvre et se referme en glissant. `interpolate-size` autorise
	 * l'interpolation vers `auto` : sans lui, aucune transition de hauteur n'est
	 * possible sur un contenu dont on ignore la taille. Là encore, les
	 * navigateurs qui ne suivent pas gardent l'ouverture instantanée native.
	 */
	@supports (interpolate-size: allow-keywords) and selector(::details-content) {
		.faq__item {
			interpolate-size: allow-keywords;
		}

		.faq__item::details-content {
			block-size: 0;
			overflow: hidden;
			transition:
				block-size 280ms cubic-bezier(0.22, 1, 0.36, 1),
				content-visibility 280ms allow-discrete;
		}

		.faq__item[open]::details-content {
			block-size: auto;
		}
	}
}

/* ------------------------------------------------------------------
 * Bandeau de secteurs défilant
 *
 * Sans JavaScript, la liste reste centrée et statique — c'est le rendu
 * d'origine. Quand main.js a pu la dupliquer, elle se met à défiler en
 * continu : les deux exemplaires se suivent, et chacun se translate de sa
 * propre largeur plus l'écart. Au moment où le premier sort du cadre, le
 * second occupe exactement sa place, et l'animation reprend sans rupture.
 * ------------------------------------------------------------------ */
.marquee-viewport {
	display: flex;
	gap: var(--space-xl);
	overflow: hidden;
	/* Les bords s'effacent au lieu d'être coupés net. */
	mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}

.marquee-viewport .marquee {
	flex: none;
	flex-wrap: nowrap;
	justify-content: flex-start;
	width: max-content;
}

@keyframes kiaros-marquee {
	to {
		transform: translateX(calc(-100% - var(--space-xl)));
	}
}

@keyframes kiaros-drift {
	to {
		background-position: 120px 120px, 64px 0, 0 64px;
	}
}

@media screen and (prefers-reduced-motion: no-preference) {
	.marquee-viewport .marquee {
		animation: kiaros-marquee 42s linear infinite;
	}

	/* On s'arrête au survol : le bandeau redevient lisible. */
	.marquee-viewport:hover .marquee {
		animation-play-state: paused;
	}

	/*
	 * Fond vivant du héros. La grille existante gagne une poussière d'étoiles
	 * teintée par l'accent, et l'ensemble dérive très lentement. C'est le
	 * décor animé de la référence, obtenu sans canvas ni script : trois
	 * dégradés et une position de fond animée. Le masque, lui, ne bouge pas —
	 * d'où l'animation de `background-position` plutôt que de `transform`.
	 */
	.hero::after {
		background-image:
			radial-gradient(circle at center, color-mix(in srgb, var(--c-accent) 30%, transparent) 1px, transparent 1.5px),
			linear-gradient(var(--grid-line) 1px, transparent 1px),
			linear-gradient(90deg, var(--grid-line) 1px, transparent 1px);
		background-size: 120px 120px, 64px 64px, 64px 64px;
		animation: kiaros-drift 90s linear infinite;
	}
}

/* ---------- Démonstration du héros ----------
   Un cycle de 9 s : le balayage descend, quatre zones s'allument, quatre
   champs se remplissent derrière elles, puis le tout se réinitialise.

   Les instants sont écrits en pourcentages du cycle et non en délais. Un
   délai avec « infinite » ne décale que la première itération : les éléments
   garderaient leur phase mais ne se réinitialiseraient pas ensemble, et la
   séquence se déferait à chaque tour. Chaque élément a donc ses propres
   images-clés, calées sur le même cycle. */
@media screen and (prefers-reduced-motion: no-preference) {
	.demo__scan {
		animation: demo-scan 9s linear infinite;
	}

	.demo__zone,
	.demo__summary {
		opacity: 0;
	}

	/* La ligne et son intitulé restent en place ; seule la valeur se remplit.
	   Un panneau vide au début du cycle passerait pour une page cassée. */
	.demo__field .demo__val,
	.demo__field .demo__score {
		opacity: 0;
	}

	.demo__zone--1 { animation: demo-zone-1 9s ease-out infinite; }
	.demo__zone--2 { animation: demo-zone-2 9s ease-out infinite; }
	.demo__zone--3 { animation: demo-zone-3 9s ease-out infinite; }
	.demo__zone--4 { animation: demo-zone-4 9s ease-out infinite; }

	.demo__field--1 .demo__val,
	.demo__field--1 .demo__score { animation: demo-field-1 9s ease-out infinite; }
	.demo__field--2 .demo__val,
	.demo__field--2 .demo__score { animation: demo-field-2 9s ease-out infinite; }
	.demo__field--3 .demo__val,
	.demo__field--3 .demo__score { animation: demo-field-3 9s ease-out infinite; }
	.demo__field--4 .demo__val,
	.demo__field--4 .demo__score { animation: demo-field-4 9s ease-out infinite; }

	.demo__summary { animation: demo-summary 9s ease-out infinite; }

	.demo__summary-dot {
		animation: kiaros-pulse 2.4s ease-in-out infinite;
	}
}

@keyframes demo-scan {
	0%   { transform: translateY(0); opacity: 0; }
	3%   { opacity: 0.85; }
	46%  { transform: translateY(258px); opacity: 0.85; }
	52%  { transform: translateY(258px); opacity: 0; }
	100% { transform: translateY(0); opacity: 0; }
}

@keyframes demo-zone-1 {
	0%, 10%    { opacity: 0; transform: scale(0.94); }
	14%, 92%   { opacity: 1; transform: scale(1); }
	97%, 100%  { opacity: 0; transform: scale(1); }
}

@keyframes demo-zone-2 {
	0%, 21%    { opacity: 0; transform: scale(0.94); }
	25%, 92%   { opacity: 1; transform: scale(1); }
	97%, 100%  { opacity: 0; transform: scale(1); }
}

@keyframes demo-zone-3 {
	0%, 32%    { opacity: 0; transform: scale(0.94); }
	36%, 92%   { opacity: 1; transform: scale(1); }
	97%, 100%  { opacity: 0; transform: scale(1); }
}

@keyframes demo-zone-4 {
	0%, 43%    { opacity: 0; transform: scale(0.94); }
	47%, 92%   { opacity: 1; transform: scale(1); }
	97%, 100%  { opacity: 0; transform: scale(1); }
}

@keyframes demo-field-1 {
	0%, 14%    { opacity: 0; transform: translateY(0.3rem); }
	19%, 92%   { opacity: 1; transform: none; }
	97%, 100%  { opacity: 0; transform: none; }
}

@keyframes demo-field-2 {
	0%, 25%    { opacity: 0; transform: translateY(0.3rem); }
	30%, 92%   { opacity: 1; transform: none; }
	97%, 100%  { opacity: 0; transform: none; }
}

@keyframes demo-field-3 {
	0%, 36%    { opacity: 0; transform: translateY(0.3rem); }
	41%, 92%   { opacity: 1; transform: none; }
	97%, 100%  { opacity: 0; transform: none; }
}

@keyframes demo-field-4 {
	0%, 47%    { opacity: 0; transform: translateY(0.3rem); }
	52%, 92%   { opacity: 1; transform: none; }
	97%, 100%  { opacity: 0; transform: none; }
}

@keyframes demo-summary {
	0%, 57%    { opacity: 0; transform: translateY(0.3rem); }
	62%, 92%   { opacity: 1; transform: none; }
	97%, 100%  { opacity: 0; transform: none; }
}

/* ---------- Démonstrations de pôle ----------
   Même cycle de 9 s et même principe que le panneau du héros : les instants
   sont écrits en pourcentages, jamais en délais. Un délai avec « infinite » ne
   décale que la première itération — les éléments garderaient leur phase mais
   ne se réinitialiseraient pas ensemble, et la séquence se déferait à chaque
   tour. Chaque élément a donc ses propres images-clés sur le cycle commun.

   Tout est sous « no-preference » et sous « screen » : à l'impression comme
   pour qui a demandé moins d'animations, l'état par défaut — l'état final —
   reste affiché tel quel. */
@media screen and (prefers-reduced-motion: no-preference) {

	/* --- Prévision : le passé se trace, la bande s'ouvre, la projection suit --- */
	.demo-prevision__past {
		stroke-dasharray: 210;
		stroke-dashoffset: 210;
		animation: demo-trace-passe 9s ease-out infinite;
	}

	.demo-prevision__future {
		opacity: 0;
		animation: demo-trace-futur 9s ease-out infinite;
	}

	.demo-prevision__band {
		transform: scaleX(0);
		animation: demo-bande 9s ease-out infinite;
	}

	.demo-prevision__dots circle {
		opacity: 0;
		animation: demo-point 9s ease-out infinite;
	}

	.demo-prevision__row--1 .demo__val,
	.demo-prevision__row--1 .demo__score { animation: demo-field-1 9s ease-out infinite; }
	.demo-prevision__row--2 .demo__val,
	.demo-prevision__row--2 .demo__score { animation: demo-field-2 9s ease-out infinite; }
	.demo-prevision__row--3 .demo__val,
	.demo-prevision__row--3 .demo__score { animation: demo-field-3 9s ease-out infinite; }

	/* --- Core Web Vitals : la jauge part du rouge et se resserre au vert --- */
	.demo-vitals__apres,
	.demo-vitals__fleche {
		opacity: 0;
	}

	.demo-vitals__item--1 .demo-vitals__jauge { animation: demo-jauge-1 9s cubic-bezier(0.22, 1, 0.36, 1) infinite; }
	.demo-vitals__item--2 .demo-vitals__jauge { animation: demo-jauge-2 9s cubic-bezier(0.22, 1, 0.36, 1) infinite; }
	.demo-vitals__item--3 .demo-vitals__jauge { animation: demo-jauge-3 9s cubic-bezier(0.22, 1, 0.36, 1) infinite; }

	.demo-vitals__item--1 :is(.demo-vitals__apres, .demo-vitals__fleche) { animation: demo-field-1 9s ease-out infinite; }
	.demo-vitals__item--2 :is(.demo-vitals__apres, .demo-vitals__fleche) { animation: demo-field-2 9s ease-out infinite; }
	.demo-vitals__item--3 :is(.demo-vitals__apres, .demo-vitals__fleche) { animation: demo-field-3 9s ease-out infinite; }

	/* --- Maquette : les blocs s'allument, le code s'écrit en regard --- */
	.demo-maquette__bloc { opacity: 0; }

	.demo-maquette__bloc--1 { animation: demo-zone-1 9s ease-out infinite; }
	.demo-maquette__bloc--2 { animation: demo-zone-2 9s ease-out infinite; }
	.demo-maquette__bloc--3,
	.demo-maquette__bloc--4 { animation: demo-zone-3 9s ease-out infinite; }
	.demo-maquette__bloc--5 { animation: demo-zone-4 9s ease-out infinite; }

	/* Comme pour les champs du héros : la ligne et son numéro restent en place,
	   seul ce qu'elle contient s'écrit. Faire disparaître la ligne entière
	   laissait la colonne de droite vide une partie du cycle, ce qui se lit
	   comme un panneau cassé. */
	.demo-maquette__ligne :is(.demo-maquette__tag, .demo-maquette__note) {
		opacity: 0;
	}

	.demo-maquette__ligne--1 :is(.demo-maquette__tag, .demo-maquette__note) { animation: demo-ligne-1 9s ease-out infinite; }
	.demo-maquette__ligne--2 :is(.demo-maquette__tag, .demo-maquette__note) { animation: demo-ligne-2 9s ease-out infinite; }
	.demo-maquette__ligne--3 :is(.demo-maquette__tag, .demo-maquette__note) { animation: demo-ligne-3 9s ease-out infinite; }
	.demo-maquette__ligne--4 :is(.demo-maquette__tag, .demo-maquette__note) { animation: demo-ligne-4 9s ease-out infinite; }
}

@keyframes demo-trace-passe {
	0%   { stroke-dashoffset: 210; }
	26%  { stroke-dashoffset: 0; }
	92%  { stroke-dashoffset: 0; }
	100% { stroke-dashoffset: 210; }
}

@keyframes demo-trace-futur {
	0%, 30% { opacity: 0; }
	46%     { opacity: 1; }
	92%     { opacity: 1; }
	100%    { opacity: 0; }
}

@keyframes demo-bande {
	0%, 28% { transform: scaleX(0); }
	50%     { transform: scaleX(1); }
	92%     { transform: scaleX(1); }
	100%    { transform: scaleX(0); }
}

@keyframes demo-point {
	0%, 34% { opacity: 0; }
	52%     { opacity: 1; }
	92%     { opacity: 1; }
	100%    { opacity: 0; }
}

/* La jauge part large — au-delà du seuil, donc en rouge — puis se resserre.
   La couleur suit la largeur : c'est le passage du seuil qui la fait basculer. */
@keyframes demo-jauge-1 {
	0%, 12%  { width: 88%; background: var(--c-danger); }
	40%      { width: 26%; background: var(--c-success); }
	92%      { width: 26%; background: var(--c-success); }
	100%     { width: 88%; background: var(--c-danger); }
}

@keyframes demo-jauge-2 {
	0%, 20%  { width: 72%; background: var(--c-danger); }
	48%      { width: 22%; background: var(--c-success); }
	92%      { width: 22%; background: var(--c-success); }
	100%     { width: 72%; background: var(--c-danger); }
}

@keyframes demo-jauge-3 {
	0%, 28%  { width: 64%; background: var(--c-danger); }
	56%      { width: 14%; background: var(--c-success); }
	92%      { width: 14%; background: var(--c-success); }
	100%     { width: 64%; background: var(--c-danger); }
}

@keyframes demo-ligne-1 {
	0%, 14% { opacity: 0; transform: translateY(6px); }
	24%     { opacity: 1; transform: translateY(0); }
	92%     { opacity: 1; transform: translateY(0); }
	100%    { opacity: 0; transform: translateY(6px); }
}

@keyframes demo-ligne-2 {
	0%, 28% { opacity: 0; transform: translateY(6px); }
	38%     { opacity: 1; transform: translateY(0); }
	92%     { opacity: 1; transform: translateY(0); }
	100%    { opacity: 0; transform: translateY(6px); }
}

@keyframes demo-ligne-3 {
	0%, 42% { opacity: 0; transform: translateY(6px); }
	52%     { opacity: 1; transform: translateY(0); }
	92%     { opacity: 1; transform: translateY(0); }
	100%    { opacity: 0; transform: translateY(6px); }
}

@keyframes demo-ligne-4 {
	0%, 56% { opacity: 0; transform: translateY(6px); }
	66%     { opacity: 1; transform: translateY(0); }
	92%     { opacity: 1; transform: translateY(0); }
	100%    { opacity: 0; transform: translateY(6px); }
}

/* ---------- Rotation des démonstrations du héros ----------
   Cinq démonstrations, neuf secondes chacune : quarante-cinq secondes de cycle.
   Ce découpage n'est pas décoratif — il met les deux animations en phase. Une
   démonstration occupe exactement un cycle de sa propre séquence, et comme
   45 = 5 × 9, elle reprend au début pile au moment où elle apparaît. Changer
   l'une des deux durées sans l'autre désynchroniserait tout.

   La transition passe par « visibility » autant que par « opacity » : un
   panneau à opacité nulle reste lu par les lecteurs d'écran, et cinq
   descriptions empilées valent moins que rien. Pendant un fondu, la règle CSS
   sur « visibility » garde l'élément visible tant qu'une des deux extrémités
   l'est, donc le fondu se voit bien. */
@media screen and (prefers-reduced-motion: no-preference) {
	.hero-rotation__item {
		display: grid;
		opacity: 0;
		visibility: hidden;
		animation: hero-rotation-2 45s linear infinite;
	}

	/* main.js pilote : la rotation automatique du CSS se retire, la classe
	   « is-active » décide seule. Sans ce garde-fou, les deux se disputeraient
	   l'opacité et un panneau choisi à la main disparaîtrait au créneau suivant. */
	:root[data-kiaros-carousel] .hero-rotation__item {
		animation: none;
		transition: opacity 420ms cubic-bezier(0.22, 1, 0.36, 1);
	}

	.hero-rotation__item--1 { animation-name: hero-rotation-1; }
	.hero-rotation__item--2 { animation-name: hero-rotation-2; }
	.hero-rotation__item--3 { animation-name: hero-rotation-3; }
	.hero-rotation__item--4 { animation-name: hero-rotation-4; }
	.hero-rotation__item--5 { animation-name: hero-rotation-5; }
}

@keyframes hero-rotation-1 {
	0%, 18%    { opacity: 1; visibility: visible; }
	21%, 98%   { opacity: 0; visibility: hidden; }
	100%       { opacity: 1; visibility: visible; }
}

@keyframes hero-rotation-2 {
	0%, 18%    { opacity: 0; visibility: hidden; }
	21%, 38%   { opacity: 1; visibility: visible; }
	41%, 100%  { opacity: 0; visibility: hidden; }
}

@keyframes hero-rotation-3 {
	0%, 38%    { opacity: 0; visibility: hidden; }
	41%, 58%   { opacity: 1; visibility: visible; }
	61%, 100%  { opacity: 0; visibility: hidden; }
}

@keyframes hero-rotation-4 {
	0%, 58%    { opacity: 0; visibility: hidden; }
	61%, 78%   { opacity: 1; visibility: visible; }
	81%, 100%  { opacity: 0; visibility: hidden; }
}

@keyframes hero-rotation-5 {
	0%, 78%    { opacity: 0; visibility: hidden; }
	81%, 98%   { opacity: 1; visibility: visible; }
	100%       { opacity: 0; visibility: hidden; }
}

/* ---------- Dix démonstrations de plus ----------
   Presque aucune image-clé nouvelle : les dix réutilisent les séquences déjà
   écrites pour la facture — « demo-field-1..4 » pour ce qui se remplit,
   « demo-zone-1..4 » pour ce qui s'allume. Elles partagent donc exactement la
   même cadence, ce qui se voit : le carrousel a un rythme, pas quinze.

   Ne sont ajoutées ici que les trois mécaniques qu'aucune séquence existante
   ne savait décrire : des plis qui tombent, des barres qui poussent, une
   courbe qui se trace. */
@media screen and (prefers-reduced-motion: no-preference) {

	/* --- Courrier : les plis quittent la pile un à un --- */
	.demo-courrier__pli--1 { animation: demo-pli-1 9s ease-in infinite; }
	.demo-courrier__pli--2 { animation: demo-pli-2 9s ease-in infinite; }
	.demo-courrier__pli--3 { animation: demo-pli-3 9s ease-in infinite; }
	.demo-courrier__pli--4 { animation: demo-pli-4 9s ease-in infinite; }

	/* --- Tickets, RGPD, API : la ligne reste, sa pastille arrive --- */
	.demo-tickets__item .demo-chip,
	.demo-rgpd__item .demo-chip,
	.demo-api__ligne :is(.demo-chip, code) {
		opacity: 0;
	}

	.demo-tickets__item--1 .demo-chip,
	.demo-rgpd__item--1 .demo-chip,
	.demo-api__ligne--1 :is(.demo-chip, code) { animation: demo-field-1 9s ease-out infinite; }

	.demo-tickets__item--2 .demo-chip,
	.demo-rgpd__item--2 .demo-chip,
	.demo-api__ligne--2 :is(.demo-chip, code) { animation: demo-field-2 9s ease-out infinite; }

	.demo-tickets__item--3 .demo-chip,
	.demo-rgpd__item--3 .demo-chip,
	.demo-api__ligne--3 :is(.demo-chip, code) { animation: demo-field-3 9s ease-out infinite; }

	.demo-tickets__item--4 .demo-chip,
	.demo-rgpd__item--4 .demo-chip,
	.demo-api__ligne--4 :is(.demo-chip, code) { animation: demo-field-4 9s ease-out infinite; }

	/* --- Assistant : la question, puis la réponse, puis ses sources --- */
	.demo-dialogue__bulle--question { animation: demo-field-1 9s ease-out infinite; }
	.demo-dialogue__bulle--reponse  { animation: demo-field-2 9s ease-out infinite; }
	.demo-dialogue__sources         { animation: demo-field-4 9s ease-out infinite; }

	.demo-dialogue__bulle,
	.demo-dialogue__sources { opacity: 0; }

	/* --- Recherche et recommandation : le point de départ, puis les résultats --- */
	.demo-requete,
	.demo-reco__depart {
		opacity: 0;
		animation: demo-field-1 9s ease-out infinite;
	}

	/* --- Scoring : les barres poussent, les valeurs suivent --- */
	.demo-scoring__barre { transform: scaleX(0); }

	.demo-scoring__item--1 .demo-scoring__barre { animation: demo-barre 9s cubic-bezier(0.22, 1, 0.36, 1) infinite; }
	.demo-scoring__item--2 .demo-scoring__barre { animation: demo-barre 9s cubic-bezier(0.22, 1, 0.36, 1) infinite 0.6s; }
	.demo-scoring__item--3 .demo-scoring__barre { animation: demo-barre 9s cubic-bezier(0.22, 1, 0.36, 1) infinite 1.2s; }
	.demo-scoring__item--4 .demo-scoring__barre { animation: demo-barre 9s cubic-bezier(0.22, 1, 0.36, 1) infinite 1.8s; }

	.demo-scoring__valeur { opacity: 0; }

	.demo-scoring__item--1 .demo-scoring__valeur { animation: demo-field-1 9s ease-out infinite; }
	.demo-scoring__item--2 .demo-scoring__valeur { animation: demo-field-2 9s ease-out infinite; }
	.demo-scoring__item--3 .demo-scoring__valeur { animation: demo-field-3 9s ease-out infinite; }
	.demo-scoring__item--4 .demo-scoring__valeur { animation: demo-field-4 9s ease-out infinite; }

	/* --- Supervision : la courbe se trace, le pic s'allume --- */
	.demo-monitoring__ligne {
		stroke-dasharray: 340;
		stroke-dashoffset: 340;
		animation: demo-trace-passe 9s ease-out infinite;
	}

	.demo-monitoring__pic {
		opacity: 0;
		animation: demo-point 9s ease-out infinite;
	}
}

/* Un pli quitte la pile : il glisse vers sa corbeille et s'efface en chemin.
   Quatre départs échelonnés sur le cycle, comme les quatre champs de la
   facture — même rythme, autre geste. */
@keyframes demo-pli-1 {
	0%, 10%  { transform: translate(0, 0); opacity: 1; }
	24%      { transform: translate(-46px, 118px) scale(0.7); opacity: 0; }
	92%      { transform: translate(-46px, 118px) scale(0.7); opacity: 0; }
	100%     { transform: translate(0, 0); opacity: 1; }
}

@keyframes demo-pli-2 {
	0%, 24%  { transform: translate(0, 0); opacity: 1; }
	38%      { transform: translate(6px, 110px) scale(0.7); opacity: 0; }
	92%      { transform: translate(6px, 110px) scale(0.7); opacity: 0; }
	100%     { transform: translate(0, 0); opacity: 1; }
}

@keyframes demo-pli-3 {
	0%, 38%  { transform: translate(0, 0); opacity: 1; }
	52%      { transform: translate(52px, 102px) scale(0.7); opacity: 0; }
	92%      { transform: translate(52px, 102px) scale(0.7); opacity: 0; }
	100%     { transform: translate(0, 0); opacity: 1; }
}

@keyframes demo-pli-4 {
	0%, 52%  { transform: translate(0, 0); opacity: 1; }
	66%      { transform: translate(-46px, 94px) scale(0.7); opacity: 0; }
	92%      { transform: translate(-46px, 94px) scale(0.7); opacity: 0; }
	100%     { transform: translate(0, 0); opacity: 1; }
}

@keyframes demo-barre {
	0%, 8%  { transform: scaleX(0); }
	32%     { transform: scaleX(1); }
	92%     { transform: scaleX(1); }
	100%    { transform: scaleX(0); }
}

/* ---------- Troisième lot ----------
   Toujours le même cycle de neuf secondes et les mêmes séquences partagées.
   Quatre mécaniques nouvelles seulement : un tracé qui se démêle, une couleur
   qui se propage, un doublon qui se replie, des barres qui poussent depuis le
   bas. */
@media screen and (prefers-reduced-motion: no-preference) {

	/* --- Tournées : le brouillon apparaît, puis le bon tracé le remplace --- */
	.demo-tournees__brouillon {
		animation: demo-brouillon 9s ease-in-out infinite;
	}

	.demo-tournees__optimise {
		stroke-dasharray: 900;
		stroke-dashoffset: 900;
		animation: demo-tournee 9s ease-out infinite;
	}

	/* Les arrêts restent visibles d'un bout à l'autre : ce sont les adresses à
	   desservir, la donnée du problème. Seul le trajet qui les relie change.
	   Les faire apparaître en cours de cycle laissait croire qu'ils faisaient
	   partie du résultat. */

	/* --- Système de design : la couleur se propage d'un coup aux six --- */
	.demo-ds { animation: demo-jeton 9s ease-in-out infinite; }

	/* --- Analyse : les cellules se corrigent, le doublon se replie --- */
	.demo-analyse__cel--corrige { opacity: 0; }

	.demo-analyse__ligne--1 .demo-analyse__cel--corrige { animation: demo-field-1 9s ease-out infinite; }
	.demo-analyse__ligne--2 .demo-analyse__cel--corrige { animation: demo-field-2 9s ease-out infinite; }
	.demo-analyse__ligne--3 .demo-analyse__cel--corrige { animation: demo-field-4 9s ease-out infinite; }

	.demo-analyse__ligne--doublon {
		display: grid;
		color: var(--c-danger);
		transform-origin: top;
		animation: demo-doublon 9s ease-in-out infinite;
	}

	/* --- Tests : les verdicts tombent un à un --- */
	.demo-tests__item :is(.demo-tests__marque, .demo-tests__duree) { opacity: 0; }

	.demo-tests__item--1 :is(.demo-tests__marque, .demo-tests__duree) { animation: demo-field-1 9s ease-out infinite; }
	.demo-tests__item--2 :is(.demo-tests__marque, .demo-tests__duree) { animation: demo-field-2 9s ease-out infinite; }
	.demo-tests__item--3 :is(.demo-tests__marque, .demo-tests__duree) { animation: demo-field-3 9s ease-out infinite; }
	.demo-tests__item--4 :is(.demo-tests__marque, .demo-tests__duree) { animation: demo-field-4 9s ease-out infinite; }

	/* --- Mise en ligne : les étages s'allument dans l'ordre --- */
	.demo-deploiement__voyant { opacity: 0; }

	.demo-deploiement__etage--1 .demo-deploiement__voyant { animation: demo-field-1 9s ease-out infinite; }
	.demo-deploiement__etage--2 .demo-deploiement__voyant { animation: demo-field-2 9s ease-out infinite; }
	.demo-deploiement__etage--3 .demo-deploiement__voyant { animation: demo-field-3 9s ease-out infinite; }
	.demo-deploiement__etage--4 .demo-deploiement__voyant { animation: demo-field-4 9s ease-out infinite; }

	/* --- Tableaux de bord : sources, puis tuiles, puis histogramme --- */
	.demo-tableaux__sources { opacity: 0; animation: demo-field-1 9s ease-out infinite; }

	.demo-tableaux__tuile { opacity: 0; }
	.demo-tableaux__tuile--1 { animation: demo-field-2 9s ease-out infinite; }
	.demo-tableaux__tuile--2 { animation: demo-field-3 9s ease-out infinite; }
	.demo-tableaux__tuile--3 { animation: demo-field-4 9s ease-out infinite; }

	.demo-tableaux__barres span {
		transform: scaleY(0);
		animation: demo-colonne 9s cubic-bezier(0.22, 1, 0.36, 1) infinite;
	}

	.demo-tableaux__barres span:nth-child(2) { animation-delay: 0.15s; }
	.demo-tableaux__barres span:nth-child(3) { animation-delay: 0.3s; }
	.demo-tableaux__barres span:nth-child(4) { animation-delay: 0.45s; }
	.demo-tableaux__barres span:nth-child(5) { animation-delay: 0.6s; }
	.demo-tableaux__barres span:nth-child(6) { animation-delay: 0.75s; }

	/* --- Silos : les liens se tracent, les nœuds apparaissent par étage --- */
	.demo-seo__lien {
		stroke-dasharray: 120;
		stroke-dashoffset: 120;
		animation: demo-fil 9s ease-out infinite;
	}

	.demo-seo__lien--4,
	.demo-seo__lien--5 { animation-name: demo-fil-tard; }

	.demo-seo__noeud { opacity: 0; }
	.demo-seo__noeud--racine { animation: demo-field-1 9s ease-out infinite; }
	.demo-seo__noeud--1,
	.demo-seo__noeud--2,
	.demo-seo__noeud--3 { animation: demo-field-2 9s ease-out infinite; }
	.demo-seo__noeud--4,
	.demo-seo__noeud--5 { animation: demo-field-4 9s ease-out infinite; }

	/* --- Audit : la part automatisable se mesure --- */
	.demo-audit__jauge {
		transform: scaleX(0);
		animation: demo-barre 9s cubic-bezier(0.22, 1, 0.36, 1) infinite;
	}
}

/* Le tracé absurde n'existe que le temps de montrer ce qu'on corrige. */
@keyframes demo-brouillon {
	0%, 6%  { opacity: 0; }
	14%     { opacity: 0.85; }
	34%     { opacity: 0.85; }
	44%     { opacity: 0; }
	100%    { opacity: 0; }
}

@keyframes demo-tournee {
	0%, 40% { stroke-dashoffset: 900; }
	72%     { stroke-dashoffset: 0; }
	92%     { stroke-dashoffset: 0; }
	100%    { stroke-dashoffset: 900; }
}

/* La couleur du jeton : trois valeurs, et les six composants suivent. */
@keyframes demo-jeton {
	0%, 24%  { --demo-ds-accent: #ffb020; }
	40%, 56% { --demo-ds-accent: #4ecf9a; }
	72%, 92% { --demo-ds-accent: #6f9fe8; }
	100%     { --demo-ds-accent: #ffb020; }
}

/* Le doublon se replie sur la ligne qu'il duplique. */
@keyframes demo-doublon {
	/* Le repli reste visuel : la ligne réserve sa place pendant tout le cycle
	   pour ne pas déplacer la page et son point de lecture. */
	0%, 30% { opacity: 1; transform: scaleY(1); }
	52%, 92% { opacity: 0; transform: scaleY(0); }
	100% { opacity: 1; transform: scaleY(1); }
}

@keyframes demo-colonne {
	0%, 30% { transform: scaleY(0); }
	58%     { transform: scaleY(1); }
	92%     { transform: scaleY(1); }
	100%    { transform: scaleY(0); }
}

@keyframes demo-fil {
	0%, 12% { stroke-dashoffset: 120; }
	36%     { stroke-dashoffset: 0; }
	92%     { stroke-dashoffset: 0; }
	100%    { stroke-dashoffset: 120; }
}

@keyframes demo-fil-tard {
	0%, 38% { stroke-dashoffset: 120; }
	62%     { stroke-dashoffset: 0; }
	92%     { stroke-dashoffset: 0; }
	100%    { stroke-dashoffset: 120; }
}

/* ---------- Pôle gamification ---------- */
@media screen and (prefers-reduced-motion: no-preference) {

	/* --- Mise en situation : la fausse piste s'ouvre, se fait sanctionner,
	       puis cède la place à la bonne réponse. --- */
	.demo-scenario__reponse--fausse {
		display: flex;
		border-color: color-mix(in srgb, var(--c-danger) 55%, transparent);
		background: color-mix(in srgb, var(--c-danger) 10%, transparent);
		animation: demo-fausse 9s ease-out infinite;
	}

	.demo-scenario__consequence {
		display: block;
		padding: 0.35rem 0.55rem 0.35rem 2.1rem;
		font-size: 0.74rem;
		color: var(--c-danger);
		animation: demo-consequence 9s ease-out infinite;
	}

	.demo-scenario__reponse--bonne { animation: demo-bonne 9s ease-out infinite; }

	.demo-scenario__lecon { opacity: 0; animation: demo-field-4 9s ease-out infinite; }

	/* --- Activation : les barres refondues poussent par-dessus les anciennes --- */
	.demo-activation__apres { transform: scaleX(0); }
	.demo-activation__neuf { opacity: 0; }

	.demo-activation__etape--1 .demo-activation__apres { animation: demo-barre 9s cubic-bezier(0.22, 1, 0.36, 1) infinite; }
	.demo-activation__etape--2 .demo-activation__apres { animation: demo-barre 9s cubic-bezier(0.22, 1, 0.36, 1) infinite 0.35s; }
	.demo-activation__etape--3 .demo-activation__apres { animation: demo-barre 9s cubic-bezier(0.22, 1, 0.36, 1) infinite 0.7s; }
	.demo-activation__etape--4 .demo-activation__apres { animation: demo-barre 9s cubic-bezier(0.22, 1, 0.36, 1) infinite 1.05s; }

	.demo-activation__etape--1 .demo-activation__neuf { animation: demo-field-1 9s ease-out infinite; }
	.demo-activation__etape--2 .demo-activation__neuf { animation: demo-field-2 9s ease-out infinite; }
	.demo-activation__etape--3 .demo-activation__neuf { animation: demo-field-3 9s ease-out infinite; }
	.demo-activation__etape--4 .demo-activation__neuf { animation: demo-field-4 9s ease-out infinite; }

	/* --- Complétion : l'ancienne courbe plonge, la nouvelle tient --- */
	.demo-completion__avant {
		stroke-dasharray: 340;
		stroke-dashoffset: 340;
		animation: demo-trace-passe 9s ease-out infinite;
	}

	.demo-completion__apres {
		stroke-dasharray: 320;
		stroke-dashoffset: 320;
		animation: demo-trace-apres 9s ease-out infinite;
	}

	.demo-completion__fin { opacity: 0; animation: demo-point 9s ease-out infinite; }
}

@keyframes demo-fausse {
	0%, 8%   { opacity: 0; transform: translateY(6px); }
	16%, 40% { opacity: 1; transform: translateY(0); }
	48%, 100% { opacity: 0; transform: translateY(-4px); }
}

@keyframes demo-consequence {
	0%, 22%  { opacity: 0; }
	30%, 40% { opacity: 1; }
	48%, 100% { opacity: 0; }
}

@keyframes demo-bonne {
	0%, 46%  { opacity: 0; transform: translateY(6px); }
	56%      { opacity: 1; transform: translateY(0); }
	92%      { opacity: 1; transform: translateY(0); }
	100%     { opacity: 0; transform: translateY(6px); }
}

/* Le tir traverse l'écran, puis disparaît dans la cible. */
@keyframes demo-trace-apres {
	0%, 34% { stroke-dashoffset: 320; }
	62%     { stroke-dashoffset: 0; }
	92%     { stroke-dashoffset: 0; }
	100%    { stroke-dashoffset: 320; }
}

@media screen and (prefers-reduced-motion: no-preference) {
	/* --- Mini-jeu : un ennemi, six balles, puis l'explosion ---
	   Impacts à 15, 20, 25, 30, 35 et 40 % du cycle, soit une touche toutes
	   les 0,45 s. Chaque salve a sa propre séquence : un simple retard aurait
	   décalé leur réinitialisation et défait la rafale au bout d'un tour. */
	.demo-arcade__ennemi { animation: demo-arc-ennemi 9s ease-out infinite; }
	.demo-arcade__anneau { animation: demo-arc-marche 2.2s linear infinite; }
	.demo-arcade__halo   { animation: demo-arc-halo 9s ease-out infinite; }

	.demo-arcade__vie       { animation: demo-arc-vie-cadre 9s steps(1) infinite; }
	.demo-arcade__vie-jauge { animation: demo-arc-vie 9s steps(1) infinite; }

	.demo-arcade__salve--1 :is(.demo-arcade__balle, .demo-arcade__trainee) { animation: demo-arc-balle-1 9s linear infinite; }
	.demo-arcade__salve--2 :is(.demo-arcade__balle, .demo-arcade__trainee) { animation: demo-arc-balle-2 9s linear infinite; }
	.demo-arcade__salve--3 :is(.demo-arcade__balle, .demo-arcade__trainee) { animation: demo-arc-balle-3 9s linear infinite; }
	.demo-arcade__salve--4 :is(.demo-arcade__balle, .demo-arcade__trainee) { animation: demo-arc-balle-4 9s linear infinite; }
	.demo-arcade__salve--5 :is(.demo-arcade__balle, .demo-arcade__trainee) { animation: demo-arc-balle-5 9s linear infinite; }
	.demo-arcade__salve--6 :is(.demo-arcade__balle, .demo-arcade__trainee) { animation: demo-arc-balle-6 9s linear infinite; }

	/* La traînée est plus pâle et plus longue que la tête : même séquence,
	   opacité divisée. */
	.demo-arcade__trainee { opacity: 0; }
	.demo-arcade__salve--1 .demo-arcade__trainee { animation-name: demo-arc-trainee-1; }
	.demo-arcade__salve--2 .demo-arcade__trainee { animation-name: demo-arc-trainee-2; }
	.demo-arcade__salve--3 .demo-arcade__trainee { animation-name: demo-arc-trainee-3; }
	.demo-arcade__salve--4 .demo-arcade__trainee { animation-name: demo-arc-trainee-4; }
	.demo-arcade__salve--5 .demo-arcade__trainee { animation-name: demo-arc-trainee-5; }
	.demo-arcade__salve--6 .demo-arcade__trainee { animation-name: demo-arc-trainee-6; }

	.demo-arcade__etincelle--1 { animation: demo-arc-etincelle-1 9s ease-out infinite; }
	.demo-arcade__etincelle--2 { animation: demo-arc-etincelle-2 9s ease-out infinite; }
	.demo-arcade__etincelle--3 { animation: demo-arc-etincelle-3 9s ease-out infinite; }
	.demo-arcade__etincelle--4 { animation: demo-arc-etincelle-4 9s ease-out infinite; }
	.demo-arcade__etincelle--5 { animation: demo-arc-etincelle-5 9s ease-out infinite; }
	.demo-arcade__etincelle--6 { animation: demo-arc-etincelle-6 9s ease-out infinite; }

	.demo-arcade__onde  { animation: demo-arc-onde 9s ease-out infinite; }
	.demo-arcade__flash { animation: demo-arc-flash 9s ease-out infinite; }

	/* Une seule image-clé pour la gerbe : la direction et le décalage de chaque
	   éclat viennent de ses propres variables. */
	.demo-arcade__eclat {
		animation: demo-arc-eclat 9s cubic-bezier(0.16, 0.9, 0.3, 1) infinite var(--retard, 0ms);
	}

	.demo-arcade__etoiles circle {
		animation: demo-arc-etoile 3.4s ease-in-out infinite;
		animation-delay: var(--retard, 0ms);
	}

	.demo-arcade__reacteur { animation: demo-arc-reacteur 0.9s ease-in-out infinite; }
	.demo-arcade__vaisseau { animation: demo-arc-recul 9s ease-out infinite; }
	.demo-arcade__bouche   { animation: demo-arc-bouche 9s ease-out infinite; }

	.demo-arcade__score--6 { opacity: 0; }
	.demo-arcade__score--0 { animation: demo-arc-score-0 9s steps(1) infinite; }
	.demo-arcade__score--1 { animation: demo-arc-score-1 9s steps(1) infinite; }
	.demo-arcade__score--2 { animation: demo-arc-score-2 9s steps(1) infinite; }
	.demo-arcade__score--3 { animation: demo-arc-score-3 9s steps(1) infinite; }
	.demo-arcade__score--4 { animation: demo-arc-score-4 9s steps(1) infinite; }
	.demo-arcade__score--5 { animation: demo-arc-score-5 9s steps(1) infinite; }
	.demo-arcade__score--6 { animation: demo-arc-score-6 9s steps(1) infinite; }

	.demo-arcade__combo--3 { opacity: 0; }
	.demo-arcade__combo--1 { animation: demo-arc-combo-1 9s ease-out infinite; }
	.demo-arcade__combo--2 { animation: demo-arc-combo-2 9s ease-out infinite; }
	.demo-arcade__combo--3 { animation: demo-arc-combo-3 9s ease-out infinite; }
}

/* Le tiret qui défile fait tourner l'anneau sans toucher au transform, resté
   libre pour le tressaillement de l'ennemi. */
@keyframes demo-arc-marche {
	from { stroke-dashoffset: 0; }
	to   { stroke-dashoffset: 15; }
}

/* L'ennemi encaisse six fois, puis disparaît. */
@keyframes demo-arc-ennemi {
	0%, 8%    { opacity: 1; transform: scale(1); }
	15%       { transform: scale(1.14); }
	17%       { transform: scale(1); }
	20%       { transform: scale(1.14); }
	22%       { transform: scale(1); }
	25%       { transform: scale(1.14); }
	27%       { transform: scale(1); }
	30%       { transform: scale(1.14); }
	32%       { transform: scale(1); }
	35%       { transform: scale(1.14); }
	37%       { transform: scale(1); }
	39%       { opacity: 1; transform: scale(1.5); }
	43%, 100% { opacity: 0; transform: scale(0); }
}

@keyframes demo-arc-halo {
	0%, 14%  { opacity: 0; }
	15%      { opacity: 0.55; }
	18%      { opacity: 0; }
	20%      { opacity: 0.55; }
	23%      { opacity: 0; }
	25%      { opacity: 0.55; }
	28%      { opacity: 0; }
	30%      { opacity: 0.55; }
	33%      { opacity: 0; }
	35%      { opacity: 0.55; }
	38%, 100% { opacity: 0; }
}

@keyframes demo-arc-vie-cadre {
	0%, 43%   { opacity: 1; }
	44%, 100% { opacity: 0; }
}

/* Six paliers, en marches : une barre de vie qui fondrait d'un cran à l'autre
   ne dirait pas qu'elle a encaissé six coups distincts. */
@keyframes demo-arc-vie {
	0%   { transform: scaleX(1); }
	15%  { transform: scaleX(0.833); }
	20%  { transform: scaleX(0.667); }
	25%  { transform: scaleX(0.5); }
	30%  { transform: scaleX(0.333); }
	35%  { transform: scaleX(0.167); }
	40%, 100% { transform: scaleX(0); }
}

@keyframes demo-arc-balle-1 {
	0%, 9%    { opacity: 0; transform: translateX(0); }
	10%       { opacity: 1; transform: translateX(0); }
	15%       { opacity: 1; transform: translateX(126px); }
	16%, 100% { opacity: 0; transform: translateX(126px); }
}
@keyframes demo-arc-balle-2 {
	0%, 14%   { opacity: 0; transform: translateX(0); }
	15%       { opacity: 1; transform: translateX(0); }
	20%       { opacity: 1; transform: translateX(126px); }
	21%, 100% { opacity: 0; transform: translateX(126px); }
}
@keyframes demo-arc-balle-3 {
	0%, 19%   { opacity: 0; transform: translateX(0); }
	20%       { opacity: 1; transform: translateX(0); }
	25%       { opacity: 1; transform: translateX(126px); }
	26%, 100% { opacity: 0; transform: translateX(126px); }
}
@keyframes demo-arc-balle-4 {
	0%, 24%   { opacity: 0; transform: translateX(0); }
	25%       { opacity: 1; transform: translateX(0); }
	30%       { opacity: 1; transform: translateX(126px); }
	31%, 100% { opacity: 0; transform: translateX(126px); }
}
@keyframes demo-arc-balle-5 {
	0%, 29%   { opacity: 0; transform: translateX(0); }
	30%       { opacity: 1; transform: translateX(0); }
	35%       { opacity: 1; transform: translateX(126px); }
	36%, 100% { opacity: 0; transform: translateX(126px); }
}
@keyframes demo-arc-balle-6 {
	0%, 34%   { opacity: 0; transform: translateX(0); }
	35%       { opacity: 1; transform: translateX(0); }
	40%       { opacity: 1; transform: translateX(126px); }
	41%, 100% { opacity: 0; transform: translateX(126px); }
}

@keyframes demo-arc-trainee-1 {
	0%, 9%    { opacity: 0; transform: translateX(0); }
	11%       { opacity: 0.4; transform: translateX(0); }
	15%       { opacity: 0.4; transform: translateX(126px); }
	16%, 100% { opacity: 0; transform: translateX(126px); }
}
@keyframes demo-arc-trainee-2 {
	0%, 14%   { opacity: 0; transform: translateX(0); }
	16%       { opacity: 0.4; transform: translateX(0); }
	20%       { opacity: 0.4; transform: translateX(126px); }
	21%, 100% { opacity: 0; transform: translateX(126px); }
}
@keyframes demo-arc-trainee-3 {
	0%, 19%   { opacity: 0; transform: translateX(0); }
	21%       { opacity: 0.4; transform: translateX(0); }
	25%       { opacity: 0.4; transform: translateX(126px); }
	26%, 100% { opacity: 0; transform: translateX(126px); }
}
@keyframes demo-arc-trainee-4 {
	0%, 24%   { opacity: 0; transform: translateX(0); }
	26%       { opacity: 0.4; transform: translateX(0); }
	30%       { opacity: 0.4; transform: translateX(126px); }
	31%, 100% { opacity: 0; transform: translateX(126px); }
}
@keyframes demo-arc-trainee-5 {
	0%, 29%   { opacity: 0; transform: translateX(0); }
	31%       { opacity: 0.4; transform: translateX(0); }
	35%       { opacity: 0.4; transform: translateX(126px); }
	36%, 100% { opacity: 0; transform: translateX(126px); }
}
@keyframes demo-arc-trainee-6 {
	0%, 34%   { opacity: 0; transform: translateX(0); }
	36%       { opacity: 0.4; transform: translateX(0); }
	40%       { opacity: 0.4; transform: translateX(126px); }
	41%, 100% { opacity: 0; transform: translateX(126px); }
}

@keyframes demo-arc-etincelle-1 {
	0%, 14%   { opacity: 0; transform: translate(0, 0) scale(0.4); }
	16%       { opacity: 1; }
	21%, 100% { opacity: 0; transform: translate(calc(var(--dx) * 1px), calc(var(--dy) * 1px)) scale(1); }
}
@keyframes demo-arc-etincelle-2 {
	0%, 19%   { opacity: 0; transform: translate(0, 0) scale(0.4); }
	21%       { opacity: 1; }
	26%, 100% { opacity: 0; transform: translate(calc(var(--dx) * 1px), calc(var(--dy) * 1px)) scale(1); }
}
@keyframes demo-arc-etincelle-3 {
	0%, 24%   { opacity: 0; transform: translate(0, 0) scale(0.4); }
	26%       { opacity: 1; }
	31%, 100% { opacity: 0; transform: translate(calc(var(--dx) * 1px), calc(var(--dy) * 1px)) scale(1); }
}
@keyframes demo-arc-etincelle-4 {
	0%, 29%   { opacity: 0; transform: translate(0, 0) scale(0.4); }
	31%       { opacity: 1; }
	36%, 100% { opacity: 0; transform: translate(calc(var(--dx) * 1px), calc(var(--dy) * 1px)) scale(1); }
}
@keyframes demo-arc-etincelle-5 {
	0%, 34%   { opacity: 0; transform: translate(0, 0) scale(0.4); }
	36%       { opacity: 1; }
	41%, 100% { opacity: 0; transform: translate(calc(var(--dx) * 1px), calc(var(--dy) * 1px)) scale(1); }
}
@keyframes demo-arc-etincelle-6 {
	0%, 38%   { opacity: 0; transform: translate(0, 0) scale(0.4); }
	40%       { opacity: 1; }
	45%, 100% { opacity: 0; transform: translate(calc(var(--dx) * 1px), calc(var(--dy) * 1px)) scale(1); }
}

@keyframes demo-arc-onde {
	0%, 39%   { opacity: 0; transform: scale(1); }
	41%       { opacity: 0.95; }
	54%, 100% { opacity: 0; transform: scale(3.6); }
}

@keyframes demo-arc-flash {
	0%, 39%   { opacity: 0; }
	41%       { opacity: 1; }
	50%, 100% { opacity: 0; }
}

@keyframes demo-arc-eclat {
	0%, 39%   { opacity: 0; transform: translate(0, 0) scale(0.3); }
	42%       { opacity: 1; }
	58%, 100% { opacity: 0; transform: translate(calc(var(--dx) * 1px), calc(var(--dy) * 1px)) scale(1); }
}

@keyframes demo-arc-etoile {
	0%, 100% { opacity: 0.16; }
	50%      { opacity: 0.42; }
}

@keyframes demo-arc-reacteur {
	0%, 100% { opacity: 0.3; transform: scaleX(0.7); }
	50%      { opacity: 0.7; transform: scaleX(1.15); }
}

/* Les sept valeurs de score se relaient sans transition : un compteur qui
   fondrait d'un chiffre à l'autre ferait bien moins compteur. */
@keyframes demo-arc-score-0 { 0%, 14% { opacity: 1; } 15%, 100% { opacity: 0; } }
@keyframes demo-arc-score-1 { 0%, 14% { opacity: 0; } 15%, 19% { opacity: 1; } 20%, 100% { opacity: 0; } }
@keyframes demo-arc-score-2 { 0%, 19% { opacity: 0; } 20%, 24% { opacity: 1; } 25%, 100% { opacity: 0; } }
@keyframes demo-arc-score-3 { 0%, 24% { opacity: 0; } 25%, 29% { opacity: 1; } 30%, 100% { opacity: 0; } }
@keyframes demo-arc-score-4 { 0%, 29% { opacity: 0; } 30%, 34% { opacity: 1; } 35%, 100% { opacity: 0; } }
@keyframes demo-arc-score-5 { 0%, 34% { opacity: 0; } 35%, 39% { opacity: 1; } 40%, 100% { opacity: 0; } }
@keyframes demo-arc-score-6 { 0%, 39% { opacity: 0; } 40%, 100% { opacity: 1; } }

@keyframes demo-arc-combo-1 {
	0%, 19%   { opacity: 0; transform: scale(0.7); }
	22%       { opacity: 1; transform: scale(1.15); }
	24%, 29%  { opacity: 1; transform: scale(1); }
	30%, 100% { opacity: 0; transform: scale(0.7); }
}

@keyframes demo-arc-combo-2 {
	0%, 29%   { opacity: 0; transform: scale(0.7); }
	32%       { opacity: 1; transform: scale(1.15); }
	34%, 39%  { opacity: 1; transform: scale(1); }
	40%, 100% { opacity: 0; transform: scale(0.7); }
}

@keyframes demo-arc-combo-3 {
	0%, 39%  { opacity: 0; transform: scale(0.7); }
	43%      { opacity: 1; transform: scale(1.3); }
	47%      { opacity: 1; transform: scale(1); }
	100%     { opacity: 1; transform: scale(1); }
}

/* Le vaisseau recule à chaque départ : six coups de rein, calés sur les six
   salves. Il remplace le bercement vertical d'avant, qui faisait naître les
   projectiles à une hauteur que le vaisseau n'occupait plus.
   
   Chaque recul part exactement à l'instant du tir — 10, 15, 20, 25, 30, 35 % —
   et non avant. Une seconde d'avance suffisait à décoller la balle de la
   pointe de trois pixels : le projectile semblait naître dans le vide. */
@keyframes demo-arc-recul {
	0%, 10%     { transform: translateX(0); }
	11.5%       { transform: translateX(-5px); }
	13.5%, 15%  { transform: translateX(0); }
	16.5%       { transform: translateX(-5px); }
	18.5%, 20%  { transform: translateX(0); }
	21.5%       { transform: translateX(-5px); }
	23.5%, 25%  { transform: translateX(0); }
	26.5%       { transform: translateX(-5px); }
	28.5%, 30%  { transform: translateX(0); }
	31.5%       { transform: translateX(-5px); }
	33.5%, 35%  { transform: translateX(0); }
	36.5%       { transform: translateX(-5px); }
	38.5%, 100% { transform: translateX(0); }
}

/* L'éclair de bouche s'allume au moment exact où chaque balle démarre. */
@keyframes demo-arc-bouche {
	0%, 9%   { opacity: 0; transform: scaleX(0.3); }
	10%      { opacity: 0.95; transform: scaleX(1); }
	12%      { opacity: 0; transform: scaleX(0.3); }
	14%      { opacity: 0; transform: scaleX(0.3); }
	15%      { opacity: 0.95; transform: scaleX(1); }
	17%      { opacity: 0; transform: scaleX(0.3); }
	19%      { opacity: 0; transform: scaleX(0.3); }
	20%      { opacity: 0.95; transform: scaleX(1); }
	22%      { opacity: 0; transform: scaleX(0.3); }
	24%      { opacity: 0; transform: scaleX(0.3); }
	25%      { opacity: 0.95; transform: scaleX(1); }
	27%      { opacity: 0; transform: scaleX(0.3); }
	29%      { opacity: 0; transform: scaleX(0.3); }
	30%      { opacity: 0.95; transform: scaleX(1); }
	32%      { opacity: 0; transform: scaleX(0.3); }
	34%      { opacity: 0; transform: scaleX(0.3); }
	35%      { opacity: 0.95; transform: scaleX(1); }
	37%, 100% { opacity: 0; transform: scaleX(0.3); }
}

/* ---------- Création de site et reprise ---------- */
@media screen and (prefers-reduced-motion: no-preference) {

	/* --- Le cadre rétrécit, et c'est la requête de conteneur qui replie la
	       maquette. Rien ici ne dessine les états : on n'anime qu'une largeur,
	       le reste suit tout seul. C'est exactement ce que fait un vrai site. --- */
	.demo-responsive__cadre { animation: demo-repli 9s cubic-bezier(0.65, 0, 0.35, 1) infinite; }

	.demo-responsive__largeur--1 { animation: demo-largeur-1 9s steps(1) infinite; }
	.demo-responsive__largeur--2 { animation: demo-largeur-2 9s steps(1) infinite; }
	.demo-responsive__largeur--3 { animation: demo-largeur-3 9s steps(1) infinite; }

	.demo-responsive__etat--1 { animation: demo-largeur-1 9s steps(1) infinite; }
	.demo-responsive__etat--2 { animation: demo-largeur-2 9s steps(1) infinite; }
	.demo-responsive__etat--3 { animation: demo-largeur-3 9s steps(1) infinite; }

	/* --- Les clés traversent une à une, de gauche à droite. --- */
	.demo-reprise__cle--1 { animation: demo-passation-1 9s cubic-bezier(0.22, 1, 0.36, 1) infinite; }
	.demo-reprise__cle--2 { animation: demo-passation-2 9s cubic-bezier(0.22, 1, 0.36, 1) infinite; }
	.demo-reprise__cle--3 { animation: demo-passation-3 9s cubic-bezier(0.22, 1, 0.36, 1) infinite; }
	.demo-reprise__cle--4 { animation: demo-passation-4 9s cubic-bezier(0.22, 1, 0.36, 1) infinite; }
	.demo-reprise__cle--5 { animation: demo-passation-5 9s cubic-bezier(0.22, 1, 0.36, 1) infinite; }

	/* Les deux en-têtes restent visibles d'un bout à l'autre : une clé qui vole
	   vers une colonne sans nom ne va nulle part. Seule sa couleur s'affirme à
	   mesure que les clés arrivent. */
	.demo-reprise__entete--apres { animation: demo-reprise-cible 9s ease-out infinite; }
}

/* Trois paliers de largeur, tenus assez longtemps pour qu'on voie la mise en
   page se réorganiser à chacun. */
@keyframes demo-repli {
	0%, 20%  { width: 100%; }
	32%, 48% { width: 62%; }
	60%, 84% { width: 34%; }
	96%, 100% { width: 100%; }
}

@keyframes demo-largeur-1 { 0%, 25% { opacity: 1; } 26%, 91% { opacity: 0; } 92%, 100% { opacity: 1; } }
@keyframes demo-largeur-2 { 0%, 25% { opacity: 0; } 26%, 53% { opacity: 1; } 54%, 100% { opacity: 0; } }
@keyframes demo-largeur-3 { 0%, 53% { opacity: 0; } 54%, 91% { opacity: 1; } 92%, 100% { opacity: 0; } }

/* La clé part de la colonne de gauche et vient se ranger à droite. Le décalage
   négatif la place chez l'ancien prestataire ; la marge finale la met chez
   vous, et c'est cette position-là qui est l'état de repos. */
@keyframes demo-passation-1 {
	0%, 6%   { transform: translateX(-100%); opacity: 0; }
	10%      { opacity: 1; }
	22%, 100% { transform: translateX(0); opacity: 1; }
}

@keyframes demo-passation-2 {
	0%, 16%  { transform: translateX(-100%); opacity: 0; }
	20%      { opacity: 1; }
	32%, 100% { transform: translateX(0); opacity: 1; }
}

@keyframes demo-passation-3 {
	0%, 26%  { transform: translateX(-100%); opacity: 0; }
	30%      { opacity: 1; }
	42%, 100% { transform: translateX(0); opacity: 1; }
}

@keyframes demo-passation-4 {
	0%, 36%  { transform: translateX(-100%); opacity: 0; }
	40%      { opacity: 1; }
	52%, 100% { transform: translateX(0); opacity: 1; }
}

@keyframes demo-passation-5 {
	0%, 46%  { transform: translateX(-100%); opacity: 0; }
	50%      { opacity: 1; }
	62%, 100% { transform: translateX(0); opacity: 1; }
}

@keyframes demo-reprise-cible {
	0%, 6%  { opacity: 0.45; }
	22%     { opacity: 1; }
	100%    { opacity: 1; }
}

/* ---------- Carrousel sur mobile ----------
   Le bloc de rotation ci-dessus affiche les panneaux empilés dans une même
   cellule et les fait défiler à l'opacité. Sur mobile, la cellule prend alors la
   hauteur de la plus grande démonstration — sept cents pixels — et les courtes
   flottent dans le vide.

   Cette feuille étant chargée après pages.css, c'est ici que la règle doit être
   reprise : un seul panneau à la fois, et le cadre suit sa hauteur. */
@media screen and (max-width: 48rem) {
	.hero-rotation__item {
		display: none;
		animation: none;
		opacity: 1;
		visibility: visible;
	}

	.hero-rotation__item--1 {
		display: grid;
	}

	:root[data-kiaros-carousel] .hero-rotation__item {
		display: none;
		opacity: 1;
		visibility: visible;
	}

	:root[data-kiaros-carousel] .hero-rotation__item.is-active {
		display: grid;
	}
}
