IT-Stock/app/static/style.css

967 lines
23 KiB
CSS
Raw Normal View History

/*
* Système de tokens : le langage visuel de l'app s'inspire des étiquettes
* d'inventaire et du geste du scanner code-barres (la ligne "laser" sous
* le champ de scan, les rayures de code-barres en motif de rupture).
*
* Pas de police externe (Google Fonts, etc.) : le poste peut tourner sur
* le réseau interne de la clinique, potentiellement sans accès internet.
* Le "caractère" typographique vient du poids, du tracking et de
* l'échelle plutôt que d'une police téléchargée.
*
* Deux couches de tokens :
* - primitives (--papier, --encre, --brique...) : les couleurs brutes,
* identiques en clair/sombre dans leur intention (ex: --brique reste
* "la couleur d'alerte" dans les deux thèmes, juste recalée en teinte).
* - sémantiques (--surface, --texte, --action-primaire...) : ce que les
* composants utilisent réellement, pour ne redéfinir qu'un seul bloc
* en mode sombre (voir prefers-color-scheme plus bas) plutôt que de
* traquer chaque usage individuel dans le fichier.
*/
:root {
--papier: #f5f3ec; /* fond, blanc "étiquette" chaud */
--papier-creux: #ebe8de; /* zones en creux (input, table stripe) */
--encre: #14181c; /* texte principal, quasi noir */
--acier: #5b6470; /* texte secondaire, bordures */
--acier-clair: #d8dbdf; /* bordures discrètes */
--brique: #b8402a; /* alerte stock bas */
--brique-doux: #f3ded9; /* fond des badges d'alerte */
--labo: #2e7d6b; /* stock ok / validation */
--labo-doux: #dfeae6; /* fond des badges "ok" */
--ambre: #c67a17; /* accent focus / scan actif */
/* --encre/--papier sont une paire "texte / fond" qui s'inverse
volontairement en mode sombre (voir plus bas). La nav et le bouton
principal ont besoin de l'effet inverse : rester une barre foncée à
texte clair dans les DEUX thèmes (sinon la nav devient une barre
claire flottant sur une page sombre). D' cette paire séparée, fixe,
jamais réassignée dans le bloc dark mode. */
--contraste: #14181c;
--contraste-texte: #f5f3ec;
/* Couche sémantique — c'est elle que les composants utilisent. */
--surface: var(--papier);
--surface-creuse: var(--papier-creux);
--surface-carte: #ffffff;
--texte: var(--encre);
--texte-secondaire: var(--acier);
--bordure: var(--acier-clair);
--accent: var(--ambre);
--action-primaire: var(--contraste);
--action-primaire-texte: var(--contraste-texte);
--danger: var(--brique);
--danger-doux: var(--brique-doux);
--succes: var(--labo);
--succes-doux: var(--labo-doux);
/* Échelle d'espacement : remplace les valeurs rem ad hoc dispersées
dans le reste du fichier, pour garder un rythme vertical cohérent. */
--espace-1: 0.25rem;
--espace-2: 0.5rem;
--espace-3: 0.75rem;
--espace-4: 1rem;
--espace-5: 1.5rem;
--espace-6: 2.5rem;
/* Échelle typographique. */
--texte-xs: 0.75rem;
--texte-sm: 0.85rem;
--texte-base: 1rem;
--texte-lg: 1.3rem;
--texte-xl: 1.75rem;
--texte-2xl: 3rem;
--rayon: 4px;
--transition: 150ms ease;
--police-corps: -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
--police-donnee: ui-monospace, "SF Mono", "Cascadia Code", "Consolas",
"Liberation Mono", monospace;
/* Sans cette déclaration, les contrôles natifs (case à cocher, flèche
de <select>, boutons +/- de <input type="number">) restent rendus en
clair par le navigateur même sur une page au thème sombre c'est ce
qui causait des champs blancs éclatants dans le formulaire "Ajouter
un matériel" en mode sombre système. */
color-scheme: light dark;
}
/*
* Mode sombre : suit uniquement la préférence système
* (prefers-color-scheme), pas de bouton de bascule ni de JS c'est du
* CSS pur, donc coût nul. Utile en pratique pour ce public (salle
* serveur, poste peu éclairé, veille de nuit). On garde les mêmes teintes
* (brique = toujours l'alerte, labo = toujours l'ok) juste recalées pour
* rester lisibles sur fond sombre.
*/
@media (prefers-color-scheme: dark) {
:root {
--papier: #1c1b17;
--papier-creux: #262420;
--encre: #f1efe6;
--acier: #9a9c93;
--acier-clair: #3a3934;
--brique: #e2694c;
--brique-doux: #3c231c;
--labo: #5fbfa4;
--labo-doux: #1d332c;
--ambre: #e0a340;
--surface-carte: #232119;
/* Un peu plus clair qu'en mode jour pour se détacher d'un fond déjà
sombre, mais reste délibérément foncé (voir commentaire sur
--contraste plus haut). */
--contraste: #2c2a22;
}
}
* {
box-sizing: border-box;
}
body {
font-family: var(--police-corps);
font-size: var(--texte-base);
line-height: 1.5;
max-width: 900px;
margin: 0 auto 4rem;
padding: 0 1rem;
color: var(--texte);
background: var(--surface);
}
h1, h2 {
font-family: var(--police-corps);
letter-spacing: -0.01em;
}
h1 {
font-size: var(--texte-xl);
}
h2 {
font-size: var(--texte-lg);
}
/* Lien d'évitement : invisible tant qu'il n'a pas le focus clavier,
permet de sauter directement au contenu sans traverser la nav à chaque
page utile sur un poste on navigue beaucoup au clavier/scan. */
.lien-evitement {
position: absolute;
left: -999px;
top: 0;
z-index: 10;
padding: var(--espace-2) var(--espace-3);
background: var(--accent);
color: var(--contraste);
font-weight: 600;
}
.lien-evitement:focus {
left: var(--espace-3);
top: var(--espace-3);
}
/* Focus clavier visible partout, indépendamment du style propre à chaque
contrôle (accessibilité : ne jamais s'appuyer sur la seule couleur). */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
/* Motif de rupture "code-barres", réutilisé en petite touche (nav, en-tête
du scanner, étiquette de résultat) : des barres de largeurs irrégulières
en dégradé répété, sans image ni police à charger. */
.rayures-code-barre {
height: 6px;
background-image: repeating-linear-gradient(
90deg,
var(--encre) 0px, var(--encre) 2px,
transparent 2px, transparent 5px,
var(--encre) 5px, var(--encre) 6px,
transparent 6px, transparent 12px,
var(--encre) 12px, var(--encre) 15px,
transparent 15px, transparent 19px
);
opacity: 0.85;
}
nav {
position: sticky;
top: 0;
z-index: 5;
display: flex;
gap: var(--espace-5);
align-items: center;
margin: 0 -1rem 2.5rem;
padding: var(--espace-4) 1rem calc(var(--espace-4) - 0.15rem);
background: var(--contraste);
}
nav a {
display: inline-flex;
align-items: center;
gap: var(--espace-2);
text-decoration: none;
color: var(--contraste-texte);
font-weight: 600;
font-size: var(--texte-sm);
letter-spacing: 0.06em;
text-transform: uppercase;
opacity: 0.75;
transition: opacity var(--transition), color var(--transition);
}
nav a svg {
flex: none;
width: 16px;
height: 16px;
}
nav a:hover {
opacity: 1;
color: var(--accent);
}
/* État actif : calculé dans base.html via request.url.path, exposé ici
par aria-current plutôt qu'une classe, pour que le style et la
sémantique accessible restent une seule et même source de vérité. */
nav a[aria-current="page"] {
opacity: 1;
color: var(--accent);
}
/*
* Disclosure native (<details>/<summary>) : utilisée partout pour replier
* par défaut ce qui n'est pas immédiatement utile (branches de
* l'arborescence, formulaire d'édition d'un matériel). Zéro JavaScript,
* et ça évite d'afficher d'un coup des centaines de champs de formulaire
* quand une liste devient longue.
*/
details > summary {
list-style: none;
cursor: pointer;
}
details > summary::-webkit-details-marker {
display: none;
}
.noeud-chevron {
flex: none;
width: 0.55em;
height: 0.55em;
border-right: 2px solid var(--texte-secondaire);
border-bottom: 2px solid var(--texte-secondaire);
transform: rotate(-45deg);
transition: transform var(--transition);
}
details[open] > summary .noeud-chevron {
transform: rotate(45deg);
}
.barre-recherche,
.barre-filtres {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--espace-3);
margin-bottom: var(--espace-4);
}
.barre-recherche input[type="search"],
.barre-filtres input[type="search"] {
flex: 1;
min-width: 200px;
max-width: 360px;
background: var(--surface-creuse);
border: none;
}
.barre-filtres select {
background: var(--surface-creuse);
border: none;
}
.case-stock-bas {
display: flex;
align-items: center;
gap: 0.4rem;
font-size: var(--texte-sm);
color: var(--texte-secondaire);
white-space: nowrap;
}
.case-stock-bas input {
width: auto;
padding: 0;
}
/* Compteur de résultats à côté des filtres : feedback simple pour savoir
qu'une recherche/filtre "vide" est bien un résultat, pas un bug. */
.compteur-resultats {
font-size: var(--texte-sm);
color: var(--texte-secondaire);
white-space: nowrap;
}
/* Fil d'Ariane de catégorie : apparaît uniquement quand /materiels est
filtré par categorie_id, pour rappeler quel sous-arbre est affiché
(le select seul ne le montre pas une fois le formulaire soumis). */
.fil-ariane-categorie {
display: flex;
align-items: center;
gap: var(--espace-2);
flex-wrap: wrap;
font-size: var(--texte-sm);
color: var(--texte-secondaire);
margin: -0.5rem 0 var(--espace-4);
}
.fil-ariane-categorie .chemin-categorie {
font-weight: 600;
color: var(--texte);
}
/* En-tête de groupe quand la liste des matériels est regroupée par
catégorie (tri=categorie) : un simple séparateur textuel, pas une carte
à part entière, pour rester léger sur une longue liste. */
.groupe-categorie-entete {
font-family: var(--police-donnee);
font-size: var(--texte-xs);
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--texte-secondaire);
padding: 0.9rem 0.5rem 0.3rem;
}
.lien-effacer {
font-size: var(--texte-sm);
color: var(--texte-secondaire);
white-space: nowrap;
}
.liste-vide {
color: var(--texte-secondaire);
font-style: italic;
padding: var(--espace-4) 0.25rem;
}
/* Liste plate (résultats de recherche de catégories, destinataires). */
.liste-resultats {
list-style: none;
margin: 0 0 1.5rem;
padding: 0;
border-top: 1px solid var(--bordure);
}
.liste-resultats li {
display: flex;
justify-content: space-between;
align-items: center;
gap: var(--espace-4);
padding: 0.6rem 0.25rem;
border-bottom: 1px solid var(--bordure);
}
.chemin-categorie {
font-family: var(--police-donnee);
font-size: 0.9rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Carte de réglage : regroupe visuellement plusieurs formulaires qui
modifient le même paramètre (ex: planification du résumé de stock, qui
se règle soit par raccourci, soit en cron libre) pour qu'ils se lisent
comme un seul réglage à deux modes de saisie plutôt que deux blocs
indépendants. */
.carte-reglage {
background: var(--surface-carte);
border: 1px solid var(--bordure);
border-radius: var(--rayon);
padding: var(--espace-4) var(--espace-5);
margin: var(--espace-4) 0 var(--espace-5);
display: flex;
flex-direction: column;
gap: var(--espace-3);
}
.carte-reglage .formulaire-planification,
.carte-reglage > form {
margin: 0;
}
.texte-secondaire {
margin: 0;
color: var(--texte-secondaire);
}
.formulaire-planification select {
min-width: 260px;
}
.champ-cron {
font-family: var(--police-donnee);
min-width: 200px;
}
/* ---------------------------------------------------------------------
Badges de statut : généralisation du "point + libellé" déjà utilisé sur
l'écran de scan (.statut-puce), réutilisé aussi dans la liste des
matériels pour que le stock bas se voie au premier coup d'œil sans
dépendre uniquement d'une couleur de texte.
------------------------------------------------------------------ */
.badge {
display: inline-flex;
align-items: center;
gap: 0.35rem;
padding: 0.15rem 0.55rem;
border-radius: 999px;
font-family: var(--police-corps);
font-size: var(--texte-xs);
font-weight: 600;
white-space: nowrap;
}
.badge-bas {
background: var(--danger-doux);
color: var(--danger);
}
.badge-ok {
background: var(--succes-doux);
color: var(--succes);
}
.statut-puce {
width: 8px;
height: 8px;
border-radius: 50%;
display: inline-block;
flex: none;
}
.badge-bas .statut-puce,
.statut-puce.statut-bas {
background: var(--danger);
}
.badge-ok .statut-puce,
.statut-puce.statut-ok {
background: var(--succes);
}
/* ---------------------------------------------------------------------
Arborescence de catégories : profondeur illimitée, repliée par défaut.
------------------------------------------------------------------ */
.arbre-categories,
.arbre-categories ul {
list-style: none;
margin: 0;
padding: 0;
}
.arbre-categories ul {
margin-left: 0.9rem;
padding-left: 0.85rem;
border-left: 1px solid var(--bordure);
}
.noeud-categorie {
margin: 0.1rem 0;
}
.noeud-categorie > details > summary {
display: flex;
align-items: center;
gap: 0.6rem;
padding: 0.4rem 0.5rem;
border-radius: var(--rayon);
transition: background var(--transition);
}
.noeud-categorie > details > summary:hover {
background: var(--surface-creuse);
}
.noeud-nom {
font-weight: 600;
}
.noeud-compte {
font-family: var(--police-donnee);
font-size: var(--texte-xs);
color: var(--texte-secondaire);
}
.noeud-actions {
display: flex;
flex-wrap: wrap;
gap: var(--espace-3);
align-items: center;
padding: 0.4rem 0.5rem 0.6rem 1.9rem;
}
.formulaire-racine {
margin-bottom: var(--espace-5);
}
/* ---------------------------------------------------------------------
Liste des matériels : lignes compactes en lecture seule, le formulaire
d'édition complet ne s'affiche qu'une fois la ligne dépliée. C'est ce
qui évite le débordement horizontal d'un tableau avec un <input> par
colonne sur chaque ligne, y compris avec une longue liste.
------------------------------------------------------------------ */
.liste-materiels {
list-style: none;
margin: 0 0 2rem;
padding: 0;
border-top: 1px solid var(--bordure);
}
.liste-materiels > li {
border-bottom: 1px solid var(--bordure);
}
.ligne-materiel > summary {
display: grid;
grid-template-columns: auto minmax(0, 2fr) minmax(0, 1.3fr) minmax(0, 1fr) auto;
align-items: center;
gap: var(--espace-3);
padding: 0.6rem 0.5rem;
transition: background var(--transition);
}
.ligne-materiel > summary:hover {
background: var(--surface-creuse);
}
.materiel-nom,
.materiel-categorie,
.materiel-code {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.materiel-nom {
font-weight: 600;
}
.materiel-categorie,
.materiel-code {
font-size: 0.8rem;
color: var(--texte-secondaire);
}
.materiel-code {
font-family: var(--police-donnee);
}
.materiel-quantite {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 0.4rem;
font-family: var(--police-donnee);
font-weight: 700;
text-align: right;
min-width: 2.5ch;
}
.materiel-quantite.quantite-basse {
color: var(--danger);
}
/* Sous ~640px, la grille 5 colonnes de la ligne de matériel n'a plus la
place de rester sur une seule ligne (le nom et la catégorie se
compressent trop). On repasse sur 2 lignes : nom + quantité au-dessus,
catégorie + code en dessous, en pleine largeur. */
@media (max-width: 640px) {
.ligne-materiel > summary {
grid-template-columns: auto 1fr auto;
grid-template-areas:
"chevron nom quantite"
"chevron meta meta";
row-gap: 0.2rem;
}
.noeud-chevron {
grid-area: chevron;
}
.materiel-nom {
grid-area: nom;
}
.materiel-quantite {
grid-area: quantite;
}
.materiel-categorie {
grid-area: meta;
}
.materiel-code {
grid-area: meta;
display: none;
}
}
.formulaire-edition {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: var(--espace-3);
padding: 0.25rem 0.5rem 1.25rem 1.9rem;
}
.formulaire-edition label {
display: flex;
flex-direction: column;
gap: 0.3rem;
font-size: 0.8rem;
color: var(--texte-secondaire);
}
.formulaire-actions {
display: flex;
gap: var(--espace-2);
align-items: end;
}
.formulaire-inline {
display: inline-flex;
gap: var(--espace-2);
align-items: center;
}
.formulaire-grille {
display: grid;
grid-template-columns: 1fr;
gap: var(--espace-3);
max-width: 400px;
}
input, select, button {
font-family: inherit;
font-size: 1rem;
padding: 0.5rem 0.6rem;
border: 1px solid var(--bordure);
border-radius: var(--rayon);
background: var(--surface-carte);
color: var(--texte);
transition: border-color var(--transition), background var(--transition), color var(--transition);
}
button {
cursor: pointer;
border-color: var(--encre);
background: var(--surface-carte);
color: var(--texte);
}
button:hover {
background: var(--surface-creuse);
}
/* Bouton principal : réservé à l'action la plus importante d'un
formulaire (créer, enregistrer). Les autres actions (filtrer,
réinitialiser, ajouter un enfant...) restent au style neutre par
défaut pour que le principal se distingue vraiment. */
.bouton-primaire {
background: var(--action-primaire);
color: var(--action-primaire-texte);
border-color: var(--action-primaire);
font-weight: 600;
}
.bouton-primaire:hover {
background: var(--accent);
border-color: var(--accent);
color: var(--contraste);
}
.bouton-danger {
background: color-mix(in srgb, var(--danger) 12%, var(--surface-carte));
color: var(--danger);
border: 1px solid color-mix(in srgb, var(--danger) 40%, var(--surface-carte));
}
.bouton-danger:hover {
background: var(--danger);
color: var(--action-primaire-texte);
}
.message-erreur {
color: var(--danger);
}
/* ---------------------------------------------------------------------
Écran de scan : le seul endroit on prend un vrai risque visuel.
Le reste de l'app reste sobre pour ne pas diluer cette signature.
------------------------------------------------------------------ */
.scanner {
max-width: 480px;
margin: 0 auto;
}
.scanner-entete {
text-align: center;
margin-bottom: var(--espace-6);
}
.scanner-eyebrow {
font-family: var(--police-donnee);
font-size: var(--texte-xs);
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--texte-secondaire);
margin: 0 0 0.25rem;
}
.scanner-entete h1 {
font-size: var(--texte-xl);
font-weight: 800;
margin: 0 0 var(--espace-4);
}
.scanner-formulaire {
position: relative;
margin-bottom: var(--espace-5);
}
.scanner-label {
display: block;
font-size: var(--texte-xs);
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--texte-secondaire);
margin-bottom: 0.4rem;
}
.scanner-champ {
width: 100%;
font-family: var(--police-donnee);
font-size: var(--texte-lg);
letter-spacing: 0.05em;
padding: 0.9rem 1rem;
background: var(--surface-creuse);
border: none;
border-radius: var(--rayon) var(--rayon) 0 0;
}
.scanner-champ::placeholder {
color: var(--texte-secondaire);
opacity: 0.7;
}
/*
* La "ligne laser" : un rappel direct du geste physique du scan (le
* faisceau rouge d'une douchette code-barres). Immobile au repos, elle ne
* s'anime que quand le champ a le focus, via le sélecteur adjacent
* input:focus + .laser-ligne (pas besoin de JavaScript).
*/
.laser-ligne {
height: 3px;
background: var(--bordure);
border-radius: 0 0 var(--rayon) var(--rayon);
overflow: hidden;
}
.scanner-champ:focus + .laser-ligne {
background: var(--accent);
animation: balayage-laser 1.6s ease-in-out infinite;
}
@keyframes balayage-laser {
0%, 100% { opacity: 0.5; }
50% { opacity: 1; }
}
/* Code scanné mais inconnu : même carte "étiquette" que le résultat d'un
scan trouvé, mais avec un formulaire de création à la place du détail
du stock pour créer le matériel sans quitter l'écran de scan. */
.etiquette-creation .etiquette-nom {
color: var(--danger);
}
.etiquette-sous-titre {
padding: 0 1.25rem 1.25rem;
color: var(--texte-secondaire);
}
.etiquette-formulaire {
padding: 0 1.25rem 1.25rem;
max-width: none;
}
/*
* L'étiquette de résultat : pensée comme une vraie étiquette d'inventaire
* qu'on viendrait de scanner, avec les rayures de code-barres en tête et
* une courte animation d'apparition ("tampon" qui vient de s'imprimer).
*/
.etiquette-resultat {
background: var(--surface-carte);
border: 1px solid var(--bordure);
border-radius: var(--rayon);
overflow: hidden;
animation: apparition-etiquette 0.25s ease-out;
}
@keyframes apparition-etiquette {
from { opacity: 0; transform: scale(0.97) translateY(4px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
.etiquette-rayures {
height: 10px;
}
.etiquette-resultat .etiquette-fil,
.etiquette-resultat .etiquette-nom,
.etiquette-resultat .etiquette-quantite,
.etiquette-resultat .etiquette-statut {
padding-left: 1.25rem;
padding-right: 1.25rem;
}
.etiquette-fil {
font-size: var(--texte-xs);
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--texte-secondaire);
margin: var(--espace-4) 0 0.15rem;
}
.etiquette-nom {
font-size: var(--texte-lg);
font-weight: 800;
margin: 0 0 var(--espace-4);
}
.etiquette-quantite {
display: flex;
align-items: baseline;
gap: var(--espace-2);
}
.etiquette-nombre {
font-family: var(--police-donnee);
font-size: var(--texte-2xl);
font-weight: 700;
line-height: 1;
}
.etiquette-unite {
color: var(--texte-secondaire);
}
.etiquette-statut {
display: flex;
align-items: center;
gap: 0.4rem;
font-size: var(--texte-sm);
color: var(--texte-secondaire);
margin: 0.6rem 0 0;
}
/*
* Deux gestes distincts sur le stock : "ajuster" (formulaire-ajustement,
* un delta relatif pas forcément ±1) et "définir" (formulaire-definir,
* une valeur absolue après un inventaire physique). Volontairement deux
* formulaires séparés pour ne jamais laisser un doute sur ce qu'un chiffre
* saisi représente.
*/
.etiquette-controles {
display: flex;
flex-direction: column;
gap: var(--espace-3);
margin-top: var(--espace-5);
padding: var(--espace-4) 1.25rem 1.25rem;
background: var(--surface-creuse);
}
.etiquette-champ-quantite {
display: flex;
flex-direction: column;
gap: 0.3rem;
font-size: var(--texte-xs);
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--texte-secondaire);
}
.etiquette-champ-quantite input {
font-family: var(--police-donnee);
font-size: 1.1rem;
background: var(--surface-carte);
}
.formulaire-definir {
display: flex;
align-items: end;
gap: var(--espace-3);
}
.formulaire-definir .etiquette-champ-quantite {
flex: 1;
}
.etiquette-actions {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1px;
background: var(--bordure);
margin-top: var(--espace-2);
}
.bouton-scan {
width: 100%;
border: none;
border-radius: 0;
padding: 0.9rem;
font-weight: 700;
letter-spacing: 0.03em;
background: var(--surface-carte);
}
.bouton-scan:hover {
background: var(--surface-creuse);
color: var(--texte);
}
.bouton-scan:active {
transform: translateY(1px);
}
.bouton-reception {
color: var(--succes);
}
.bouton-sortie {
color: var(--danger);
}
/* Respecte la préférence système : pas d'animation pour qui la désactive. */
@media (prefers-reduced-motion: reduce) {
.scanner-champ:focus + .laser-ligne {
animation: none;
}
.etiquette-resultat {
animation: none;
}
* {
transition: none !important;
}
}