/* =========================================================================
   viewer-fx.css — v6
   Effets d'apparition / disparition des elements de template.
   Partage entre le player (Viewer) et l'editeur (DiadisPlay / diadisplay.js).

   Types 1 a 19  : portage des effets jQuery UI historiques.
   Types 20 a 33 : effets ajoutes en v4 (modernes, 3D, decoupe, reflet).
   Types 34 a 36 : mises au point (flou vers net) ajoutees en v5, amplitude
                   et rayon de flou accentues en v6.

   Convention de nommage :
     .sm-fx-in-<effet>   -> apparition (equivalent .show(effet) jQuery UI)
     .sm-fx-out-<effet>  -> disparition (equivalent .hide(effet) jQuery UI)

   Deux variables sont posees par viewer-fx.js avant chaque animation :
     --sm-base-tr : le transform propre de l'element (rotate(Xdeg) du template)
     --sm-base-op : son opacite propre (curseur transparence de l'editeur)
   Sans elles, un element incline se redressait pendant l'animation, et un
   element semi-transparent remontait a 100% avant de repiquer a sa valeur.

   La duree est posee en inline par viewer-fx.js (style.animationDuration).
   Retour arriere : ne pas charger ce fichier, cf. Viewer::fxEngine().

   Cout GPU indique en tete de chaque effet ajoute en v4 :
     [A] transform + opacity uniquement -> compositeur seul, quasi gratuit
     [B] clip-path                       -> repaint du contenu, cout modere
     [C] repaint plein cadre             -> reserver aux petits elements
   ========================================================================= */

:root {
	--sm-fx-duration: 400ms;
	--sm-fx-ease-move: cubic-bezier(0.65, 0, 0.35, 1);

	/* v4 — sorties "modernes" : depart franc, arrivee tres amortie. */
	--sm-fx-ease-out: cubic-bezier(.16, 1, .3, 1);
	--sm-fx-ease-in: cubic-bezier(.7, 0, .84, 0);

	/* v4 — leger depassement, sans rebond franc. */
	--sm-fx-ease-back: cubic-bezier(.34, 1.56, .64, 1);
	--sm-fx-ease-back-in: cubic-bezier(.36, 0, .66, -.56);

	/* v4 — profondeur commune aux effets 3D. Augmenter = perspective plus douce. */
	--sm-fx-persp: 900px;
}

.sm-fx {
	animation-duration: var(--sm-fx-duration);
	animation-fill-mode: both;
	animation-timing-function: ease;
	animation-iteration-count: 1;

	/* Promotion en couche GPU pour la duree de l'animation seulement :
	   clear() retire la classe .sm-fx, donc aucune couche permanente. */
	will-change: transform, opacity;
}

/* Les effets de deplacement utilisaient easeInOutExpo cote jQuery UI */
.sm-fx-in-drop-left,
.sm-fx-in-drop-right,
.sm-fx-in-drop-up,
.sm-fx-in-drop-down,
.sm-fx-out-drop-left,
.sm-fx-out-drop-right,
.sm-fx-out-drop-up,
.sm-fx-out-drop-down,
.sm-fx-in-slide-left,
.sm-fx-in-slide-right,
.sm-fx-in-slide-up,
.sm-fx-in-slide-down,
.sm-fx-out-slide-left,
.sm-fx-out-slide-right,
.sm-fx-out-slide-up,
.sm-fx-out-slide-down {
	animation-timing-function: var(--sm-fx-ease-move);
}


/* =========================================================================
   1 — FADE
   Courbe lineaire : un fondu doit progresser au meme rythme du debut a la
   fin. Avec la courbe "ease" par defaut, l'essentiel de la variation
   d'opacite se concentrait au milieu et la transition paraissait sèche.
   ========================================================================= */

@keyframes sm-in-fade  { from { opacity: 0 } to { opacity: var(--sm-base-op, 1) } }
@keyframes sm-out-fade { from { opacity: var(--sm-base-op, 1) } to { opacity: 0 } }

.sm-fx-in-fade  { animation-name: sm-in-fade;  animation-timing-function: linear }
.sm-fx-out-fade { animation-name: sm-out-fade; animation-timing-function: linear }


/* =========================================================================
   2 — SCALE  (jQuery UI : 'scale', {percent:100})
   ========================================================================= */

@keyframes sm-in-scale {
	from { opacity: 0; transform: scale(0) var(--sm-base-tr,) }
	to   { opacity: var(--sm-base-op, 1); transform: scale(1) var(--sm-base-tr,) }
}
@keyframes sm-out-scale {
	from { opacity: var(--sm-base-op, 1); transform: scale(1) var(--sm-base-tr,) }
	to   { opacity: 0; transform: scale(0) var(--sm-base-tr,) }
}

.sm-fx-in-scale  { animation-name: sm-in-scale;  transform-origin: center }
.sm-fx-out-scale { animation-name: sm-out-scale; transform-origin: center }


/* =========================================================================
   3 a 6 — DROP  (deplacement + fondu)
   direction = le cote d'ou l'element arrive / vers lequel il part

   Le deplacement suit une courbe expo (depart et arrivee amortis), mais
   l'opacite, elle, doit progresser regulierement : laissee sur la meme
   courbe, elle restait a quasi zero pendant le premier tiers puis basculait
   d'un coup au milieu. Les paliers a 25/50/75 % la ramenent proche du
   lineaire sans toucher au mouvement.
   ========================================================================= */

@keyframes sm-in-drop-left {
	0%   { opacity: 0; transform: translateX(-100%) var(--sm-base-tr,) }
	25%  { opacity: calc(var(--sm-base-op, 1) * .2) }
	50%  { opacity: calc(var(--sm-base-op, 1) * .45) }
	75%  { opacity: calc(var(--sm-base-op, 1) * .75) }
	100% { opacity: var(--sm-base-op, 1); transform: translateX(0) var(--sm-base-tr,) }
}
@keyframes sm-out-drop-left {
	0%   { opacity: var(--sm-base-op, 1); transform: translateX(0) var(--sm-base-tr,) }
	25%  { opacity: calc(var(--sm-base-op, 1) * .75) }
	50%  { opacity: calc(var(--sm-base-op, 1) * .45) }
	75%  { opacity: calc(var(--sm-base-op, 1) * .2) }
	100% { opacity: 0; transform: translateX(-100%) var(--sm-base-tr,) }
}

@keyframes sm-in-drop-right {
	0%   { opacity: 0; transform: translateX(100%) var(--sm-base-tr,) }
	25%  { opacity: calc(var(--sm-base-op, 1) * .2) }
	50%  { opacity: calc(var(--sm-base-op, 1) * .45) }
	75%  { opacity: calc(var(--sm-base-op, 1) * .75) }
	100% { opacity: var(--sm-base-op, 1); transform: translateX(0) var(--sm-base-tr,) }
}
@keyframes sm-out-drop-right {
	0%   { opacity: var(--sm-base-op, 1); transform: translateX(0) var(--sm-base-tr,) }
	25%  { opacity: calc(var(--sm-base-op, 1) * .75) }
	50%  { opacity: calc(var(--sm-base-op, 1) * .45) }
	75%  { opacity: calc(var(--sm-base-op, 1) * .2) }
	100% { opacity: 0; transform: translateX(100%) var(--sm-base-tr,) }
}

@keyframes sm-in-drop-up {
	0%   { opacity: 0; transform: translateY(-100%) var(--sm-base-tr,) }
	25%  { opacity: calc(var(--sm-base-op, 1) * .2) }
	50%  { opacity: calc(var(--sm-base-op, 1) * .45) }
	75%  { opacity: calc(var(--sm-base-op, 1) * .75) }
	100% { opacity: var(--sm-base-op, 1); transform: translateY(0) var(--sm-base-tr,) }
}
@keyframes sm-out-drop-up {
	0%   { opacity: var(--sm-base-op, 1); transform: translateY(0) var(--sm-base-tr,) }
	25%  { opacity: calc(var(--sm-base-op, 1) * .75) }
	50%  { opacity: calc(var(--sm-base-op, 1) * .45) }
	75%  { opacity: calc(var(--sm-base-op, 1) * .2) }
	100% { opacity: 0; transform: translateY(-100%) var(--sm-base-tr,) }
}

@keyframes sm-in-drop-down {
	0%   { opacity: 0; transform: translateY(100%) var(--sm-base-tr,) }
	25%  { opacity: calc(var(--sm-base-op, 1) * .2) }
	50%  { opacity: calc(var(--sm-base-op, 1) * .45) }
	75%  { opacity: calc(var(--sm-base-op, 1) * .75) }
	100% { opacity: var(--sm-base-op, 1); transform: translateY(0) var(--sm-base-tr,) }
}
@keyframes sm-out-drop-down {
	0%   { opacity: var(--sm-base-op, 1); transform: translateY(0) var(--sm-base-tr,) }
	25%  { opacity: calc(var(--sm-base-op, 1) * .75) }
	50%  { opacity: calc(var(--sm-base-op, 1) * .45) }
	75%  { opacity: calc(var(--sm-base-op, 1) * .2) }
	100% { opacity: 0; transform: translateY(100%) var(--sm-base-tr,) }
}

.sm-fx-in-drop-left   { animation-name: sm-in-drop-left }
.sm-fx-out-drop-left  { animation-name: sm-out-drop-left }
.sm-fx-in-drop-right  { animation-name: sm-in-drop-right }
.sm-fx-out-drop-right { animation-name: sm-out-drop-right }
.sm-fx-in-drop-up     { animation-name: sm-in-drop-up }
.sm-fx-out-drop-up    { animation-name: sm-out-drop-up }
.sm-fx-in-drop-down   { animation-name: sm-in-drop-down }
.sm-fx-out-drop-down  { animation-name: sm-out-drop-down }


/* =========================================================================
   15 a 18 — SLIDE  (identique a DROP mais sans fondu, comme jQuery UI)
   ========================================================================= */

@keyframes sm-in-slide-left {
	from { transform: translateX(-100%) var(--sm-base-tr,) }
	to   { transform: translateX(0)     var(--sm-base-tr,) }
}
@keyframes sm-out-slide-left {
	from { transform: translateX(0)     var(--sm-base-tr,) }
	to   { transform: translateX(-100%) var(--sm-base-tr,) }
}

@keyframes sm-in-slide-right {
	from { transform: translateX(100%) var(--sm-base-tr,) }
	to   { transform: translateX(0)    var(--sm-base-tr,) }
}
@keyframes sm-out-slide-right {
	from { transform: translateX(0)    var(--sm-base-tr,) }
	to   { transform: translateX(100%) var(--sm-base-tr,) }
}

@keyframes sm-in-slide-up {
	from { transform: translateY(-100%) var(--sm-base-tr,) }
	to   { transform: translateY(0)     var(--sm-base-tr,) }
}
@keyframes sm-out-slide-up {
	from { transform: translateY(0)     var(--sm-base-tr,) }
	to   { transform: translateY(-100%) var(--sm-base-tr,) }
}

@keyframes sm-in-slide-down {
	from { transform: translateY(100%) var(--sm-base-tr,) }
	to   { transform: translateY(0)    var(--sm-base-tr,) }
}
@keyframes sm-out-slide-down {
	from { transform: translateY(0)    var(--sm-base-tr,) }
	to   { transform: translateY(100%) var(--sm-base-tr,) }
}

.sm-fx-in-slide-left   { animation-name: sm-in-slide-left }
.sm-fx-out-slide-left  { animation-name: sm-out-slide-left }
.sm-fx-in-slide-right  { animation-name: sm-in-slide-right }
.sm-fx-out-slide-right { animation-name: sm-out-slide-right }
.sm-fx-in-slide-up     { animation-name: sm-in-slide-up }
.sm-fx-out-slide-up    { animation-name: sm-out-slide-up }
.sm-fx-in-slide-down   { animation-name: sm-in-slide-down }
.sm-fx-out-slide-down  { animation-name: sm-out-slide-down }


/* =========================================================================
   7 / 8 — BLIND  (store qui se deroule ; jQuery UI utilisait clip)
   ========================================================================= */

@keyframes sm-in-blind-up     { from { clip-path: inset(0 0 100% 0) } to { clip-path: inset(0 0 0 0) } }
@keyframes sm-out-blind-up    { from { clip-path: inset(0 0 0 0) }    to { clip-path: inset(0 0 100% 0) } }
@keyframes sm-in-blind-down   { from { clip-path: inset(100% 0 0 0) } to { clip-path: inset(0 0 0 0) } }
@keyframes sm-out-blind-down  { from { clip-path: inset(0 0 0 0) }    to { clip-path: inset(100% 0 0 0) } }
@keyframes sm-in-blind-left   { from { clip-path: inset(0 100% 0 0) } to { clip-path: inset(0 0 0 0) } }
@keyframes sm-out-blind-left  { from { clip-path: inset(0 0 0 0) }    to { clip-path: inset(0 100% 0 0) } }
@keyframes sm-in-blind-right  { from { clip-path: inset(0 0 0 100%) } to { clip-path: inset(0 0 0 0) } }
@keyframes sm-out-blind-right { from { clip-path: inset(0 0 0 0) }    to { clip-path: inset(0 0 0 100%) } }

.sm-fx-in-blind-up     { animation-name: sm-in-blind-up }
.sm-fx-out-blind-up    { animation-name: sm-out-blind-up }
.sm-fx-in-blind-down   { animation-name: sm-in-blind-down }
.sm-fx-out-blind-down  { animation-name: sm-out-blind-down }
.sm-fx-in-blind-left   { animation-name: sm-in-blind-left }
.sm-fx-out-blind-left  { animation-name: sm-out-blind-left }
.sm-fx-in-blind-right  { animation-name: sm-in-blind-right }
.sm-fx-out-blind-right { animation-name: sm-out-blind-right }


/* =========================================================================
   9 / 19 — PULSATE  (3 pulsations, comme jQuery UI)
   ========================================================================= */

@keyframes sm-in-pulsate {
	0%   { opacity: 0 }
	20%  { opacity: var(--sm-base-op, 1) }
	35%  { opacity: calc(var(--sm-base-op, 1) * .25) }
	50%  { opacity: var(--sm-base-op, 1) }
	65%  { opacity: calc(var(--sm-base-op, 1) * .25) }
	80%  { opacity: var(--sm-base-op, 1) }
	100% { opacity: var(--sm-base-op, 1) }
}

@keyframes sm-out-pulsate {
	0%   { opacity: var(--sm-base-op, 1) }
	18%  { opacity: calc(var(--sm-base-op, 1) * .25) }
	36%  { opacity: var(--sm-base-op, 1) }
	54%  { opacity: calc(var(--sm-base-op, 1) * .25) }
	72%  { opacity: var(--sm-base-op, 1) }
	100% { opacity: 0 }
}

.sm-fx-in-pulsate  { animation-name: sm-in-pulsate }
.sm-fx-out-pulsate { animation-name: sm-out-pulsate }


/* =========================================================================
   10 — BOUNCE
   Deux choses rendaient l'ancienne version peu credible : une courbe
   unique pour toute l'animation (la chute freinait au lieu d'accelerer)
   et des rebonds regulierement espaces.

   Ici chaque segment porte sa propre courbe — chute acceleree, remontee
   freinee — et les hauteurs decroissent d'un facteur ~0,3 a chaque
   impact, avec des intervalles proportionnels a la racine de la hauteur,
   comme un vrai rebond.
   ========================================================================= */

@keyframes sm-in-bounce {
	0%   { opacity: 0; transform: translateY(-100%) var(--sm-base-tr,); animation-timing-function: cubic-bezier(.33, 0, .67, .35) }
	38%  { opacity: var(--sm-base-op, 1); transform: translateY(0) var(--sm-base-tr,); animation-timing-function: cubic-bezier(.33, .65, .67, 1) }
	56%  { transform: translateY(-22%) var(--sm-base-tr,); animation-timing-function: cubic-bezier(.33, 0, .67, .35) }
	72%  { transform: translateY(0) var(--sm-base-tr,);    animation-timing-function: cubic-bezier(.33, .65, .67, 1) }
	84%  { transform: translateY(-8%) var(--sm-base-tr,);  animation-timing-function: cubic-bezier(.33, 0, .67, .35) }
	92%  { transform: translateY(0) var(--sm-base-tr,);    animation-timing-function: cubic-bezier(.33, .65, .67, 1) }
	96%  { transform: translateY(-2.5%) var(--sm-base-tr,); animation-timing-function: cubic-bezier(.33, 0, .67, .35) }
	100% { opacity: var(--sm-base-op, 1); transform: translateY(0) var(--sm-base-tr,) }
}

@keyframes sm-out-bounce {
	0%   { opacity: var(--sm-base-op, 1); transform: translateY(0) var(--sm-base-tr,); animation-timing-function: cubic-bezier(.33, .65, .67, 1) }
	8%   { transform: translateY(-2.5%) var(--sm-base-tr,); animation-timing-function: cubic-bezier(.33, 0, .67, .35) }
	16%  { transform: translateY(0) var(--sm-base-tr,);    animation-timing-function: cubic-bezier(.33, .65, .67, 1) }
	28%  { transform: translateY(-8%) var(--sm-base-tr,);  animation-timing-function: cubic-bezier(.33, 0, .67, .35) }
	40%  { transform: translateY(0) var(--sm-base-tr,);    animation-timing-function: cubic-bezier(.33, .65, .67, 1) }
	56%  { transform: translateY(-22%) var(--sm-base-tr,); animation-timing-function: cubic-bezier(.33, 0, .67, .35) }
	68%  { opacity: var(--sm-base-op, 1); transform: translateY(0) var(--sm-base-tr,); animation-timing-function: cubic-bezier(.33, .65, .67, .9) }
	100% { opacity: 0; transform: translateY(-100%) var(--sm-base-tr,) }
}

.sm-fx-in-bounce  { animation-name: sm-in-bounce }
.sm-fx-out-bounce { animation-name: sm-out-bounce }


/* =========================================================================
   11 — EXPLODE  (approximation : jQuery UI decoupait en 9 fragments)
   ========================================================================= */

@keyframes sm-in-explode {
	from { opacity: 0; transform: scale(1.6) var(--sm-base-tr,); filter: blur(8px) }
	to   { opacity: var(--sm-base-op, 1); transform: scale(1) var(--sm-base-tr,); filter: blur(0) }
}
@keyframes sm-out-explode {
	from { opacity: var(--sm-base-op, 1); transform: scale(1) var(--sm-base-tr,); filter: blur(0) }
	to   { opacity: 0; transform: scale(1.6) var(--sm-base-tr,); filter: blur(8px) }
}

.sm-fx-in-explode  { animation-name: sm-in-explode;  transform-origin: center }
.sm-fx-out-explode { animation-name: sm-out-explode; transform-origin: center }


/* =========================================================================
   12 — FOLD  (pliage en deux temps)
   ========================================================================= */

@keyframes sm-in-fold {
	0%   { opacity: 0; transform: scaleX(0) scaleY(.03) var(--sm-base-tr,) }
	50%  { opacity: var(--sm-base-op, 1); transform: scaleX(1) scaleY(.03) var(--sm-base-tr,) }
	100% { opacity: var(--sm-base-op, 1); transform: scaleX(1) scaleY(1) var(--sm-base-tr,) }
}
@keyframes sm-out-fold {
	0%   { opacity: var(--sm-base-op, 1); transform: scaleX(1) scaleY(1) var(--sm-base-tr,) }
	50%  { opacity: var(--sm-base-op, 1); transform: scaleX(1) scaleY(.03) var(--sm-base-tr,) }
	100% { opacity: 0; transform: scaleX(0) scaleY(.03) var(--sm-base-tr,) }
}

.sm-fx-in-fold  { animation-name: sm-in-fold;  transform-origin: center }
.sm-fx-out-fold { animation-name: sm-out-fold; transform-origin: center }


/* =========================================================================
   13 — HIGHLIGHT  (flash via ::after : le fond propre n'est pas ecrase)

   Sur un element SANS fond propre (un bloc de texte pose directement sur
   le template), l'aplat jaune couvre tout le rectangle et noie le texte
   au lieu de le mettre en valeur. viewer-fx.js detecte ce cas et pose la
   classe .sm-fx-alpha : on bascule alors sur un drop-shadow, qui epouse
   exactement la forme des pixels reellement peints — les glyphes — et
   laisse le vide transparent.
   ========================================================================= */

@keyframes sm-highlight-flash {
	0%   { opacity: 0 }
	30%  { opacity: .85 }
	100% { opacity: 0 }
}

.sm-fx-in-highlight  { animation-name: sm-in-fade }
.sm-fx-out-highlight { animation-name: sm-out-fade }

.sm-fx-in-highlight::after,
.sm-fx-out-highlight::after {
	content: '';
	position: absolute;
	inset: 0;
	pointer-events: none;
	border-radius: inherit;
	background-color: #ffff99;
	animation-name: sm-highlight-flash;
	animation-duration: inherit;
	animation-fill-mode: both;
	animation-timing-function: ease;
	z-index: 1;
}

/* --- variante alpha : halo sur les pixels opaques uniquement --- */

@keyframes sm-highlight-glow {
	0%   { filter: drop-shadow(0 0 0 rgba(255, 245, 130, 0)) }
	30%  { filter: drop-shadow(0 0 .45em rgba(255, 245, 130, .95)) }
	100% { filter: drop-shadow(0 0 0 rgba(255, 245, 130, 0)) }
}

.sm-fx-alpha.sm-fx-in-highlight  { animation-name: sm-in-fade, sm-highlight-glow }
.sm-fx-alpha.sm-fx-out-highlight { animation-name: sm-out-fade, sm-highlight-glow }

.sm-fx-alpha.sm-fx-in-highlight::after,
.sm-fx-alpha.sm-fx-out-highlight::after { display: none }


/* =========================================================================
   14 — SHAKE
   ========================================================================= */

@keyframes sm-in-shake {
	0%   { opacity: 0; transform: translateX(0) var(--sm-base-tr,) }
	10%  { opacity: var(--sm-base-op, 1); transform: translateX(-6%) var(--sm-base-tr,) }
	25%  { transform: translateX(6%)  var(--sm-base-tr,) }
	40%  { transform: translateX(-5%) var(--sm-base-tr,) }
	55%  { transform: translateX(5%)  var(--sm-base-tr,) }
	70%  { transform: translateX(-3%) var(--sm-base-tr,) }
	85%  { transform: translateX(3%)  var(--sm-base-tr,) }
	100% { opacity: var(--sm-base-op, 1); transform: translateX(0) var(--sm-base-tr,) }
}

@keyframes sm-out-shake {
	0%   { opacity: var(--sm-base-op, 1); transform: translateX(0) var(--sm-base-tr,) }
	15%  { transform: translateX(3%)  var(--sm-base-tr,) }
	30%  { transform: translateX(-3%) var(--sm-base-tr,) }
	45%  { transform: translateX(5%)  var(--sm-base-tr,) }
	60%  { transform: translateX(-5%) var(--sm-base-tr,) }
	75%  { transform: translateX(6%)  var(--sm-base-tr,) }
	90%  { opacity: var(--sm-base-op, 1); transform: translateX(-6%) var(--sm-base-tr,) }
	100% { opacity: 0; transform: translateX(0) var(--sm-base-tr,) }
}

.sm-fx-in-shake  { animation-name: sm-in-shake }
.sm-fx-out-shake { animation-name: sm-out-shake }


/* =========================================================================
   20 — RISE  [A]   Montee douce + leger agrandissement.
   Le reveal sobre par defaut : lisible, jamais tape-a-l-oeil.
   Blocs de texte, titres, cartouches d'info.
   ========================================================================= */

@keyframes sm-in-rise {
	from { opacity: 0; transform: translateY(7%) scale(.97) var(--sm-base-tr,) }
	to   { opacity: var(--sm-base-op, 1); transform: translateY(0) scale(1) var(--sm-base-tr,) }
}
@keyframes sm-out-rise {
	from { opacity: var(--sm-base-op, 1); transform: translateY(0) scale(1) var(--sm-base-tr,) }
	to   { opacity: 0; transform: translateY(-7%) scale(.97) var(--sm-base-tr,) }
}

.sm-fx-in-rise  { animation-name: sm-in-rise;  animation-timing-function: var(--sm-fx-ease-out); transform-origin: center }
.sm-fx-out-rise { animation-name: sm-out-rise; animation-timing-function: var(--sm-fx-ease-in);  transform-origin: center }


/* =========================================================================
   21 — ZOOM  [A]   L'element arrive legerement trop grand et se pose.
   Le pendant "photo" de rise : visuels de spectacle, affiches.
   ========================================================================= */

@keyframes sm-in-zoom {
	from { opacity: 0; transform: scale(1.08) var(--sm-base-tr,) }
	to   { opacity: var(--sm-base-op, 1); transform: scale(1) var(--sm-base-tr,) }
}
@keyframes sm-out-zoom {
	from { opacity: var(--sm-base-op, 1); transform: scale(1) var(--sm-base-tr,) }
	to   { opacity: 0; transform: scale(1.08) var(--sm-base-tr,) }
}

.sm-fx-in-zoom  { animation-name: sm-in-zoom;  animation-timing-function: var(--sm-fx-ease-out); transform-origin: center }
.sm-fx-out-zoom { animation-name: sm-out-zoom; animation-timing-function: var(--sm-fx-ease-in);  transform-origin: center }


/* =========================================================================
   22 / 23 — GLIDE  [A]   Glisse court (8 %) + fondu.
   La version sobre de drop/slide : le mouvement donne la direction sans
   traverser l'ecran. Tres bon en cascade sur plusieurs elements (memes
   reglages, delais echelonnes de 100 a 200 ms).
   ========================================================================= */

@keyframes sm-in-glide-left {
	from { opacity: 0; transform: translateX(-8%) var(--sm-base-tr,) }
	to   { opacity: var(--sm-base-op, 1); transform: translateX(0) var(--sm-base-tr,) }
}
@keyframes sm-out-glide-left {
	from { opacity: var(--sm-base-op, 1); transform: translateX(0) var(--sm-base-tr,) }
	to   { opacity: 0; transform: translateX(-8%) var(--sm-base-tr,) }
}

@keyframes sm-in-glide-right {
	from { opacity: 0; transform: translateX(8%) var(--sm-base-tr,) }
	to   { opacity: var(--sm-base-op, 1); transform: translateX(0) var(--sm-base-tr,) }
}
@keyframes sm-out-glide-right {
	from { opacity: var(--sm-base-op, 1); transform: translateX(0) var(--sm-base-tr,) }
	to   { opacity: 0; transform: translateX(8%) var(--sm-base-tr,) }
}

.sm-fx-in-glide-left   { animation-name: sm-in-glide-left;   animation-timing-function: var(--sm-fx-ease-out) }
.sm-fx-out-glide-left  { animation-name: sm-out-glide-left;  animation-timing-function: var(--sm-fx-ease-in) }
.sm-fx-in-glide-right  { animation-name: sm-in-glide-right;  animation-timing-function: var(--sm-fx-ease-out) }
.sm-fx-out-glide-right { animation-name: sm-out-glide-right; animation-timing-function: var(--sm-fx-ease-in) }


/* =========================================================================
   24 — FLIP-X  [A]   Retournement autour de l'axe horizontal (panneau de
   gare). L'opacite est pleine des 35 % pour eviter l'impression de fondu
   pendant la rotation.
   ========================================================================= */

@keyframes sm-in-flip-x {
	0%   { opacity: 0; transform: perspective(var(--sm-fx-persp)) rotateX(-92deg) var(--sm-base-tr,) }
	35%  { opacity: var(--sm-base-op, 1) }
	100% { opacity: var(--sm-base-op, 1); transform: perspective(var(--sm-fx-persp)) rotateX(0deg) var(--sm-base-tr,) }
}
@keyframes sm-out-flip-x {
	0%   { opacity: var(--sm-base-op, 1); transform: perspective(var(--sm-fx-persp)) rotateX(0deg) var(--sm-base-tr,) }
	65%  { opacity: var(--sm-base-op, 1) }
	100% { opacity: 0; transform: perspective(var(--sm-fx-persp)) rotateX(92deg) var(--sm-base-tr,) }
}

.sm-fx-in-flip-x  { animation-name: sm-in-flip-x;  animation-timing-function: var(--sm-fx-ease-out); transform-origin: center }
.sm-fx-out-flip-x { animation-name: sm-out-flip-x; animation-timing-function: var(--sm-fx-ease-in);  transform-origin: center }


/* =========================================================================
   25 — FLIP-Y  [A]   Meme chose autour de l'axe vertical.
   ========================================================================= */

@keyframes sm-in-flip-y {
	0%   { opacity: 0; transform: perspective(var(--sm-fx-persp)) rotateY(92deg) var(--sm-base-tr,) }
	35%  { opacity: var(--sm-base-op, 1) }
	100% { opacity: var(--sm-base-op, 1); transform: perspective(var(--sm-fx-persp)) rotateY(0deg) var(--sm-base-tr,) }
}
@keyframes sm-out-flip-y {
	0%   { opacity: var(--sm-base-op, 1); transform: perspective(var(--sm-fx-persp)) rotateY(0deg) var(--sm-base-tr,) }
	65%  { opacity: var(--sm-base-op, 1) }
	100% { opacity: 0; transform: perspective(var(--sm-fx-persp)) rotateY(-92deg) var(--sm-base-tr,) }
}

.sm-fx-in-flip-y  { animation-name: sm-in-flip-y;  animation-timing-function: var(--sm-fx-ease-out); transform-origin: center }
.sm-fx-out-flip-y { animation-name: sm-out-flip-y; animation-timing-function: var(--sm-fx-ease-in);  transform-origin: center }


/* =========================================================================
   26 — DOOR  [A]   Ouverture de porte : charniere sur le bord gauche.
   Effet marquant sur un grand visuel ou un bandeau.

   La charniere n'est PAS obtenue par transform-origin : un origin sur un
   bord s'applique a la totalite du transform compose, donc aussi au
   rotate() propre du template, qui se met alors a pivoter autour du bord
   au lieu du centre — l'element saute des la premiere frame.

   On garde donc l'origine au centre (celle du rendu statique) et on
   deplace le pivot par encadrement : translateX(-50%) ... translateX(50%).
   C'est l'identite mathematique d'un changement d'origine, et les deux
   translations s'annulent exactement a l'angle 0 : l'element part et
   arrive pile a sa position de repos, rotation de template comprise.
   ========================================================================= */

@keyframes sm-in-door {
	0%   { opacity: 0; transform: perspective(var(--sm-fx-persp)) translateX(-50%) rotateY(-105deg) translateX(50%) var(--sm-base-tr,) }
	30%  { opacity: var(--sm-base-op, 1) }
	100% { opacity: var(--sm-base-op, 1); transform: perspective(var(--sm-fx-persp)) translateX(-50%) rotateY(0deg) translateX(50%) var(--sm-base-tr,) }
}
@keyframes sm-out-door {
	0%   { opacity: var(--sm-base-op, 1); transform: perspective(var(--sm-fx-persp)) translateX(-50%) rotateY(0deg) translateX(50%) var(--sm-base-tr,) }
	70%  { opacity: var(--sm-base-op, 1) }
	100% { opacity: 0; transform: perspective(var(--sm-fx-persp)) translateX(-50%) rotateY(-105deg) translateX(50%) var(--sm-base-tr,) }
}

.sm-fx-in-door  { animation-name: sm-in-door;  animation-timing-function: var(--sm-fx-ease-out); transform-origin: 50% 50% }
.sm-fx-out-door { animation-name: sm-out-door; animation-timing-function: var(--sm-fx-ease-in);  transform-origin: 50% 50% }


/* =========================================================================
   27 — STAND  [A]   L'element est couche a plat puis se redresse ; a la
   sortie il bascule vers le spectateur. Charniere sur le bord bas,
   obtenue par le meme encadrement que door (cf. note ci-dessus).
   ========================================================================= */

@keyframes sm-in-stand {
	0%   { opacity: 0; transform: perspective(var(--sm-fx-persp)) translateY(50%) rotateX(88deg) translateY(-50%) var(--sm-base-tr,) }
	30%  { opacity: var(--sm-base-op, 1) }
	100% { opacity: var(--sm-base-op, 1); transform: perspective(var(--sm-fx-persp)) translateY(50%) rotateX(0deg) translateY(-50%) var(--sm-base-tr,) }
}
@keyframes sm-out-stand {
	0%   { opacity: var(--sm-base-op, 1); transform: perspective(var(--sm-fx-persp)) translateY(50%) rotateX(0deg) translateY(-50%) var(--sm-base-tr,) }
	60%  { opacity: var(--sm-base-op, 1) }
	100% { opacity: 0; transform: perspective(var(--sm-fx-persp)) translateY(50%) rotateX(-88deg) translateY(-50%) var(--sm-base-tr,) }
}

.sm-fx-in-stand  { animation-name: sm-in-stand;  animation-timing-function: var(--sm-fx-ease-out); transform-origin: 50% 50% }
.sm-fx-out-stand { animation-name: sm-out-stand; animation-timing-function: var(--sm-fx-ease-in);  transform-origin: 50% 50% }


/* =========================================================================
   28 — POP  [A]   Apparition ressort, le depassement etant porte par la
   courbe d'easing (2 keyframes seulement : le moins cher du lot).
   ========================================================================= */

@keyframes sm-in-pop {
	from { opacity: 0; transform: scale(.35) var(--sm-base-tr,) }
	to   { opacity: var(--sm-base-op, 1); transform: scale(1) var(--sm-base-tr,) }
}
@keyframes sm-out-pop {
	from { opacity: var(--sm-base-op, 1); transform: scale(1) var(--sm-base-tr,) }
	to   { opacity: 0; transform: scale(.35) var(--sm-base-tr,) }
}

.sm-fx-in-pop  { animation-name: sm-in-pop;  animation-timing-function: var(--sm-fx-ease-back);    transform-origin: center }
.sm-fx-out-pop { animation-name: sm-out-pop; animation-timing-function: var(--sm-fx-ease-back-in); transform-origin: center }


/* =========================================================================
   29 — JELLY  [A]   Elastique : l'element depasse puis se stabilise en
   deux oscillations. Pour attirer l'oeil sur un prix, une date, un
   "COMPLET".
   ========================================================================= */

@keyframes sm-in-jelly {
	0%   { opacity: 0; transform: scale(.5) var(--sm-base-tr,) }
	50%  { opacity: var(--sm-base-op, 1); transform: scale(1.09) var(--sm-base-tr,) }
	70%  { transform: scale(.95) var(--sm-base-tr,) }
	85%  { transform: scale(1.03) var(--sm-base-tr,) }
	100% { opacity: var(--sm-base-op, 1); transform: scale(1) var(--sm-base-tr,) }
}
@keyframes sm-out-jelly {
	0%   { opacity: var(--sm-base-op, 1); transform: scale(1) var(--sm-base-tr,) }
	15%  { transform: scale(1.03) var(--sm-base-tr,) }
	30%  { transform: scale(.95) var(--sm-base-tr,) }
	50%  { opacity: var(--sm-base-op, 1); transform: scale(1.09) var(--sm-base-tr,) }
	100% { opacity: 0; transform: scale(.5) var(--sm-base-tr,) }
}

.sm-fx-in-jelly  { animation-name: sm-in-jelly;  animation-timing-function: ease-out; transform-origin: center }
.sm-fx-out-jelly { animation-name: sm-out-jelly; animation-timing-function: ease-in;  transform-origin: center }


/* =========================================================================
   30 / 31 — SPLIT  [B]   Rideau qui s'ouvre depuis le centre.
   -h : ouverture gauche/droite. -v : ouverture haut/bas.
   Sur un element a coins arrondis, ajouter " round 12px" dans les inset()
   pour que la decoupe suive le rayon.
   ========================================================================= */

@keyframes sm-in-split-h  { from { clip-path: inset(0 50% 0 50%) } to { clip-path: inset(0 0 0 0) } }
@keyframes sm-out-split-h { from { clip-path: inset(0 0 0 0) }     to { clip-path: inset(0 50% 0 50%) } }
@keyframes sm-in-split-v  { from { clip-path: inset(50% 0 50% 0) } to { clip-path: inset(0 0 0 0) } }
@keyframes sm-out-split-v { from { clip-path: inset(0 0 0 0) }     to { clip-path: inset(50% 0 50% 0) } }

.sm-fx-in-split-h  { animation-name: sm-in-split-h;  animation-timing-function: var(--sm-fx-ease-out) }
.sm-fx-out-split-h { animation-name: sm-out-split-h; animation-timing-function: var(--sm-fx-ease-in) }
.sm-fx-in-split-v  { animation-name: sm-in-split-v;  animation-timing-function: var(--sm-fx-ease-out) }
.sm-fx-out-split-v { animation-name: sm-out-split-v; animation-timing-function: var(--sm-fx-ease-in) }


/* =========================================================================
   32 — WIPE  [B]   Balayage diagonal, le bord haut menant la marche.
   Le plus "broadcast" du lot : tres bien sur un bandeau plein largeur.
   Les 4 sommets sont conserves d'un keyframe a l'autre : condition pour
   que clip-path interpole au lieu de sauter.
   ========================================================================= */

@keyframes sm-in-wipe {
	from { clip-path: polygon(0 0, 0 0, -25% 100%, 0 100%) }
	to   { clip-path: polygon(0 0, 125% 0, 100% 100%, 0 100%) }
}
@keyframes sm-out-wipe {
	from { clip-path: polygon(0 0, 125% 0, 100% 100%, 0 100%) }
	to   { clip-path: polygon(125% 0, 125% 0, 100% 100%, 100% 100%) }
}

.sm-fx-in-wipe  { animation-name: sm-in-wipe;  animation-timing-function: var(--sm-fx-ease-out) }
.sm-fx-out-wipe { animation-name: sm-out-wipe; animation-timing-function: var(--sm-fx-ease-in) }


/* =========================================================================
   33 — SHINE  [C]   Fondu double d'un reflet qui balaie l'element.
   Meme mecanique que HIGHLIGHT (pseudo-element ::after) mais en verre
   plutot qu'en surligneur. A RESERVER aux elements de taille modeste
   (logo, titre, cartouche) : le balayage repeint le cadre a chaque frame.
   ========================================================================= */

@keyframes sm-shine-sweep {
	0%   { background-position: 210% 0; opacity: 0 }
	18%  { opacity: 1 }
	82%  { opacity: 1 }
	100% { background-position: -70% 0; opacity: 0 }
}

.sm-fx-in-shine  { animation-name: sm-in-fade }
.sm-fx-out-shine { animation-name: sm-out-fade }

.sm-fx-in-shine::after,
.sm-fx-out-shine::after {
	content: '';
	position: absolute;
	inset: 0;
	pointer-events: none;
	z-index: 1;
	border-radius: inherit;
	background-image: linear-gradient(105deg,
		rgba(255, 255, 255, 0) 42%,
		rgba(255, 255, 255, .55) 50%,
		rgba(255, 255, 255, 0) 58%);
	background-size: 250% 100%;
	background-repeat: no-repeat;
	animation-name: sm-shine-sweep;
	animation-duration: inherit;
	animation-fill-mode: both;
	animation-timing-function: linear;
}


/* --- variante alpha : reflet qui traverse les pixels opaques --- */

/* Le balayage devient un halo mobile : drop-shadow suit le canal alpha,
   donc la lumiere ne se pose que sur les glyphes et jamais sur le vide.
   Les deux animations tournent en parallele (fondu + reflet) : la duree
   posee en inline par viewer-fx.js s'applique aux deux. */

@keyframes sm-shine-glow {
	0%   { filter: drop-shadow(-.55em 0 .3em rgba(255, 255, 255, 0)) }
	20%  { filter: drop-shadow(-.28em 0 .3em rgba(255, 255, 255, .85)) }
	80%  { filter: drop-shadow(.28em 0 .3em rgba(255, 255, 255, .85)) }
	100% { filter: drop-shadow(.55em 0 .3em rgba(255, 255, 255, 0)) }
}

.sm-fx-alpha.sm-fx-in-shine  { animation-name: sm-in-fade, sm-shine-glow }
.sm-fx-alpha.sm-fx-out-shine { animation-name: sm-out-fade, sm-shine-glow }

.sm-fx-alpha.sm-fx-in-shine::after,
.sm-fx-alpha.sm-fx-out-shine::after { display: none }


/* =========================================================================
   34 a 36 — FOCUS  [C]   Mise au point : l'element arrive flou et devient
   net (et repart net vers le flou). Le deplacement d'echelle est volontai-
   rement faible — c'est la nettete qui porte l'effet, pas le zoom.

     34 focus-fwd  : vient de loin et avance vers le spectateur
     35 focus-back : vient de pres et recule a sa place
     36 focus      : mise au point seule, sans deplacement

   Le flou est le seul filtre couteux du fichier : chaque frame repasse par
   une convolution. Parfait sur un titre ou un cartouche, a eviter sur un
   visuel plein ecran d'un boitier d'entree de gamme (cf. .sm-fx-low en fin
   de fichier). Le rayon se regle par --sm-fx-blur.
   ========================================================================= */

:root {
	--sm-fx-blur: 18px;
}

@keyframes sm-in-focus-fwd {
	from { opacity: 0; transform: scale(.78) var(--sm-base-tr,); filter: blur(var(--sm-fx-blur)) }
	to   { opacity: var(--sm-base-op, 1); transform: scale(1) var(--sm-base-tr,); filter: blur(0) }
}
@keyframes sm-out-focus-fwd {
	from { opacity: var(--sm-base-op, 1); transform: scale(1) var(--sm-base-tr,); filter: blur(0) }
	to   { opacity: 0; transform: scale(1.28) var(--sm-base-tr,); filter: blur(var(--sm-fx-blur)) }
}

@keyframes sm-in-focus-back {
	from { opacity: 0; transform: scale(1.28) var(--sm-base-tr,); filter: blur(var(--sm-fx-blur)) }
	to   { opacity: var(--sm-base-op, 1); transform: scale(1) var(--sm-base-tr,); filter: blur(0) }
}
@keyframes sm-out-focus-back {
	from { opacity: var(--sm-base-op, 1); transform: scale(1) var(--sm-base-tr,); filter: blur(0) }
	to   { opacity: 0; transform: scale(.78) var(--sm-base-tr,); filter: blur(var(--sm-fx-blur)) }
}

@keyframes sm-in-focus {
	from { opacity: 0; filter: blur(var(--sm-fx-blur)) }
	to   { opacity: var(--sm-base-op, 1); filter: blur(0) }
}
@keyframes sm-out-focus {
	from { opacity: var(--sm-base-op, 1); filter: blur(0) }
	to   { opacity: 0; filter: blur(var(--sm-fx-blur)) }
}

.sm-fx-in-focus-fwd   { animation-name: sm-in-focus-fwd;   animation-timing-function: var(--sm-fx-ease-out); transform-origin: center }
.sm-fx-out-focus-fwd  { animation-name: sm-out-focus-fwd;  animation-timing-function: var(--sm-fx-ease-in);  transform-origin: center }
.sm-fx-in-focus-back  { animation-name: sm-in-focus-back;  animation-timing-function: var(--sm-fx-ease-out); transform-origin: center }
.sm-fx-out-focus-back { animation-name: sm-out-focus-back; animation-timing-function: var(--sm-fx-ease-in);  transform-origin: center }
.sm-fx-in-focus       { animation-name: sm-in-focus;       animation-timing-function: var(--sm-fx-ease-out) }
.sm-fx-out-focus      { animation-name: sm-out-focus;      animation-timing-function: var(--sm-fx-ease-in) }

/* =========================================================================
   Garde-fous
   ========================================================================= */

/* Sans cela, un demi-tour laisse apparaitre le dos de l'element (texte en
   miroir) sur une frame ou deux selon le pilote graphique. */
.sm-fx-in-scale,   .sm-fx-out-scale,
.sm-fx-in-fold,    .sm-fx-out-fold,
.sm-fx-in-explode, .sm-fx-out-explode,
.sm-fx-in-rise,    .sm-fx-out-rise,
.sm-fx-in-zoom,    .sm-fx-out-zoom,
.sm-fx-in-flip-x,  .sm-fx-out-flip-x,
.sm-fx-in-flip-y,  .sm-fx-out-flip-y,
.sm-fx-in-door,    .sm-fx-out-door,
.sm-fx-in-stand,   .sm-fx-out-stand,
.sm-fx-in-pop,     .sm-fx-out-pop,
.sm-fx-in-jelly,   .sm-fx-out-jelly {
	backface-visibility: hidden;
}

/* Editeur : pendant une previsualisation, on neutralise le survol
   qui ajouterait un box-shadow parasite au milieu de l'animation. */
.element.sm-fx:hover,
.sm-fx.element:hover {
	box-shadow: none;
}

/* Sur un boitier faible, le flou d'explode peut faire chuter le framerate :
   decommenter pour le neutraliser sans changer le reste.
.sm-fx-in-explode,
.sm-fx-out-explode { filter: none !important }
*/

/* Ecran poussif : degrade les 4 effets 3D et les 3 mises au point en
   simple fondu, et supprime le reflet, sans toucher aux autres. Activer en
   posant la classe .sm-fx-low sur <body> (par ecran : une colonne
   ad_ecrans, ou ?fxlow=1).
.sm-fx-low .sm-fx-in-flip-x,     .sm-fx-low .sm-fx-in-flip-y,
.sm-fx-low .sm-fx-in-door,       .sm-fx-low .sm-fx-in-stand,
.sm-fx-low .sm-fx-in-focus-fwd,  .sm-fx-low .sm-fx-in-focus-back,
.sm-fx-low .sm-fx-in-focus       { animation-name: sm-in-fade; filter: none }
.sm-fx-low .sm-fx-out-flip-x,    .sm-fx-low .sm-fx-out-flip-y,
.sm-fx-low .sm-fx-out-door,      .sm-fx-low .sm-fx-out-stand,
.sm-fx-low .sm-fx-out-focus-fwd, .sm-fx-low .sm-fx-out-focus-back,
.sm-fx-low .sm-fx-out-focus      { animation-name: sm-out-fade; filter: none }
.sm-fx-low .sm-fx-in-shine::after,
.sm-fx-low .sm-fx-out-shine::after { display: none }
*/
