/* =========================================================================
   SWISS PRO MOVING — components.css
   Les classes des composants d'ÉCRAN. Préfixe `spm-`.

   Deux règles de fabrication, tenues par `_outils/controle.py` :
     1. aucune couleur en dur — tout est `var(--…)` ;
     2. aucun jeton employé ici qui ne soit défini dans tokens.css.
        Ce fichier ne déclare donc AUCUNE variable à lui.

   Ce fichier ne redessine rien que tokens.css §4 porte déjà. Trois formes de
   marque y vivent et se COMPOSENT avec les classes d'ici :
     `.coupe-rabat`  le coin coupé d'une image   → <img class="coupe-rabat">
     `.pli`          l'épaulement du bloc rouge  → <section class="spm-bloc-marque pli">
     `.puce`         la puce en rabat            → <li class="puce">
   Elles ne sont pas recopiées : une règle, un seul code.

   Documents imprimés (A4, carte de visite, étiquette, signature d'e-mail) :
   voir `documents.css`.
   ========================================================================= */

/* ===================================================== 1. BOUTONS ======= */

/* Libellé : verbe + objet, casse de phrase. « Demander un devis », jamais
   « DEMANDER UN DEVIS » ni « Demander Un Devis » (fondations.md §2.5).
   Un seul bouton principal par écran : c'est le seul aplat rouge autorisé,
   et il ne coexiste pas avec un grand aplat de marque (§1.5). */

.spm-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--e8);
  min-height: 48px;
  padding: var(--btn-principal-marge-i);
  border: var(--filet-epaisseur) solid transparent;
  border-radius: var(--btn-principal-rayon);
  font-family: var(--police-texte);
  font-size: var(--t-texte);
  font-weight: var(--btn-principal-graisse);
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  box-shadow: var(--ombre);
  transition:
    background-color var(--duree-instant) var(--entree),
    border-color var(--duree-instant) var(--entree),
    color var(--duree-instant) var(--entree);
}

/* Le bouton ne grossit jamais au survol : il déplacerait ce qui l'entoure
   (fondations.md §4.2). Seule la couleur change. */

.spm-btn--primaire {
  background-color: var(--btn-principal-fond);
  color: var(--btn-principal-texte);
  border-color: var(--btn-principal-filet);
}

.spm-btn--primaire:hover { background-color: var(--btn-principal-fond-survol); }
.spm-btn--primaire:active { background-color: var(--btn-principal-fond-appui); }

/* En mode sombre, le survol ASSOMBRIT le rouge lui aussi — l'éclaircir est
   impossible : du blanc sur `--rouge-500` ne donne que 3,84:1. Le filet de
   1 px en `--marque` garantit alors la limite du bouton contre le fond
   (3,95:1) quand l'aplat descend sous `rouge-600` (fondations.md §1.3,
   décision 2). tokens.css ne porte pas de valeur sombre pour
   `--btn-principal-filet` : elle est posée ici, et nulle part ailleurs. */
[data-theme="dark"] .spm-btn--primaire { border-color: var(--marque); }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .spm-btn--primaire { border-color: var(--marque); }
}

.spm-btn--secondaire {
  background-color: var(--btn-second-fond);
  color: var(--btn-second-texte);
  border-color: var(--btn-second-filet);
}

.spm-btn--secondaire:hover { background-color: var(--btn-second-fond-survol); }

/* Fantôme : une action de troisième rang. Pas de fond, pas de filet. */
.spm-btn--fantome {
  background-color: transparent;
  color: var(--marque-texte);
  border-color: transparent;
  padding-inline: var(--e8);
}

.spm-btn--fantome:hover { text-decoration: underline; text-underline-offset: .18em; }

.spm-btn--petit {
  min-height: 40px;
  padding: var(--e8) var(--e16);
  font-size: var(--t-petit);
}

.spm-btn--grand {
  min-height: 56px;
  padding: var(--e24) var(--e32);
  font-size: var(--t-texte-large);
}

/* Pleine largeur : sur mobile, et dans une colonne de formulaire. */
.spm-btn--bloc { display: flex; width: 100%; }

.spm-btn[disabled],
.spm-btn[aria-disabled="true"] {
  background-color: var(--surface-enfoncee);
  color: var(--texte-faible);
  border-color: var(--filet);
  cursor: not-allowed;
}

/* Groupe de boutons : l'action principale d'abord, dans le sens de lecture. */
.spm-btns {
  display: flex;
  flex-wrap: wrap;
  gap: var(--e8);
  align-items: center;
}

/* ============================================== 2. CHAMPS ET FORMULAIRES = */

/* Jamais un formulaire mort : chaque formulaire a une destination, un bouton
   d'envoi au libellé explicite, et une mention nLPD dessous (voir README,
   « Composer un formulaire »). L'indication (`placeholder`) ne remplace
   JAMAIS l'étiquette : elle montre un format, « 1206 Genève », « 12,5 m³ ». */

.spm-formulaire {
  display: grid;
  gap: var(--e24);
  max-width: var(--mesure-etroite);
}

.spm-groupe {
  display: grid;
  gap: var(--e4);
}

.spm-etiquette {
  font-family: var(--police-texte);
  font-size: var(--t-texte);
  font-weight: var(--graisse-medium);
  color: var(--texte);
}

/* L'astérisque est interdit (plateforme.md §5). Un champ facultatif se dit :
   <span class="spm-facultatif">facultatif</span> */
.spm-facultatif {
  font-weight: var(--graisse-normale);
  font-size: var(--t-petit);
  color: var(--texte-faible);
}

.spm-champ {
  width: 100%;
  min-height: 48px;
  padding: var(--champ-marge-i);
  font-family: var(--police-texte);
  font-size: var(--t-texte);
  line-height: var(--interligne-texte);
  color: var(--champ-texte);
  background-color: var(--champ-fond);
  border: var(--filet-epaisseur) solid var(--champ-filet);
  border-radius: var(--champ-rayon);
  box-shadow: var(--ombre);
  transition: border-color var(--duree-instant) var(--entree);
}

.spm-champ::placeholder {
  color: var(--champ-indication);
  opacity: 1;                     /* Firefox éclaircit l'indication par défaut */
}

.spm-champ:focus { border-color: var(--champ-filet-actif); }

textarea.spm-champ {
  min-height: 128px;
  resize: vertical;
}

/* Un nombre saisi est une mesure : m³, CHF, étage, code postal, téléphone. */
.spm-champ--nombre {
  font-family: var(--police-chiffre);
  font-variant-numeric: tabular-nums lining-nums;
}

.spm-champ--erreur { border-color: var(--erreur); }

/* Aide de champ : le format attendu, la limite, la raison de la demande. */
.spm-aide {
  font-size: var(--t-petit);
  line-height: var(--interligne-petit);
  color: var(--texte-secondaire);
}

/* Message d'erreur d'un champ. Le bloc d'erreur, lui, est `.spm-alerte--erreur`. */
.spm-erreur-champ {
  font-size: var(--t-petit);
  line-height: var(--interligne-petit);
  color: var(--erreur);
  font-weight: var(--graisse-medium);
}

/* Case à cocher et bouton radio : la case garde la taille du doigt, et
   l'étiquette est cliquable avec elle. */
.spm-case {
  display: flex;
  align-items: flex-start;
  gap: var(--e12);
  font-size: var(--t-texte);
  cursor: pointer;
}

.spm-case input {
  flex: none;
  width: 20px;
  height: 20px;
  margin: 0;
  accent-color: var(--marque);
}

/* Mention nLPD sous le formulaire : obligatoire, jamais en gris pâle. */
.spm-nlpd {
  font-size: var(--t-micro);
  line-height: 1.4;
  color: var(--texte-secondaire);
  max-width: var(--mesure);
}

/* ===================================================== 3. TABLEAU ======= */

/* Lignes fines, aucune alternance de fond, aucune bordure verticale : la
   colonne se lit par l'alignement, pas par une grille. Les chiffres sont en
   DIN, à chasse fixe, alignés à DROITE (fondations.md §2.4 et §3.3). */

.spm-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--t-texte);
}

.spm-table caption {
  text-align: left;
  font-weight: var(--graisse-medium);
  color: var(--texte-secondaire);
  padding-bottom: var(--e12);
}

.spm-table th {
  font-family: var(--police-texte);
  font-weight: var(--graisse-medium);
  font-size: var(--t-petit);
  text-align: left;
  color: var(--texte-secondaire);
  padding: var(--e12) var(--e16);
  border-bottom: var(--filet-epaisseur) solid var(--filet-fort);
  vertical-align: bottom;
}

/* tokens.css §4 met TOUTES les cellules `td` en DIN. Dans un tableau de
   prestations, la colonne de description est du texte et non une mesure :
   fondations.md §2.4 réserve la DIN aux nombres (« Pas dans un paragraphe »).
   On rend donc la famille de texte aux cellules, et on la reprend en DIN sur
   les seules cellules marquées `.nombre`. C'est un écart volontaire à
   tokens.css, et le seul de ce fichier. */
.spm-table td {
  font-family: var(--police-texte);
  font-weight: var(--graisse-normale);
  padding: var(--e12) var(--e16);
  border-bottom: var(--filet-epaisseur) solid var(--filet);
  vertical-align: top;
}

.spm-table th.nombre,
.spm-table td.nombre {
  font-family: var(--police-chiffre);
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: "tnum" 1, "lnum" 1;
  text-align: right;
  white-space: nowrap;
}

/* Première et dernière colonne à fleur de bloc : le tableau n'est pas encadré. */
.spm-table th:first-child,
.spm-table td:first-child { padding-inline-start: 0; }

.spm-table th:last-child,
.spm-table td:last-child { padding-inline-end: 0; }

.spm-table tbody tr:last-child td { border-bottom: 0; }

/* Ligne de total : un filet en `--texte` la sépare, la graisse la tient.
   Aucun quatrième filet n'est inventé (fondations.md §3.5). */
.spm-table__total td {
  border-top: var(--filet-epaisseur) solid var(--texte);
  border-bottom: 0;
  padding-top: var(--e16);
  font-weight: var(--graisse-gras);
  color: var(--texte);
}

/* Un tableau large tient sur 375 px en défilant seul, jamais en débordant. */
.spm-table-defile {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

/* ===================================================== 4. CARTE ========= */

/* Une carte se détache parce qu'elle est cernée ou parce que sa surface
   change, jamais parce qu'elle flotte : le système n'a aucune ombre
   (fondations.md §3.6). Jamais de carte dans une carte. */

.spm-carte {
  padding: var(--bloc-marge-i);
  background-color: var(--bloc-fond);
  border: var(--filet-epaisseur) solid var(--bloc-filet);
  border-radius: var(--rayon);
  box-shadow: var(--bloc-ombre);
}

.spm-carte__titre {
  font-family: var(--police-texte);
  font-weight: var(--graisse-gras);
  font-size: var(--t-h3);
  line-height: var(--interligne-h3);
  margin: 0 0 var(--e12);
}

.spm-carte__corps > * + * { margin-top: var(--e12); }

/* Carte nue : cernée sur le fond de page, sans surface teintée. */
.spm-carte--nue { background-color: transparent; }

/* Grille de cartes sans point de rupture. */
.spm-cartes {
  display: grid;
  gap: var(--gouttiere);
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}

/* ===================================================== 5. BADGE ========= */

/* Un badge est une étiquette d'état, pas un bouton et pas une décoration.
   Le badge de marque n'est JAMAIS un aplat rouge plein : l'aplat plein est
   réservé au bouton principal ou au bloc de marque (fondations.md §1.5). */

.spm-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--e4);
  padding: var(--e4) var(--e8);
  border: var(--filet-epaisseur) solid currentColor;
  border-radius: var(--rayon);
  font-family: var(--police-texte);
  font-size: var(--t-micro);
  font-weight: var(--graisse-medium);
  line-height: 1.3;
  white-space: nowrap;
}

.spm-badge--marque {
  background-color: var(--marque-voile);
  color: var(--texte-sur-voile);
}

.spm-badge--succes {
  background-color: var(--succes-surface);
  color: var(--succes);
}

.spm-badge--attention {
  background-color: var(--attention-surface);
  color: var(--attention);
}

.spm-badge--neutre {
  background-color: var(--surface-enfoncee);
  color: var(--texte-secondaire);
}

/* ===================================================== 6. ALERTE ======== */

/* L'erreur et la marque ne peuvent JAMAIS prendre la forme l'une de l'autre
   (fondations.md §1.4) :
       aplat rouge plein   marque oui · erreur jamais
       filet gauche 3 px   marque jamais · erreur oui
       porte du blanc      marque oui · erreur jamais
       peut être un bouton marque oui · erreur jamais
   Le filet gauche de 3 px appartient à l'erreur, et à rien d'autre : le
   succès et l'attention reçoivent un filet complet de 1 px.
   Et aucun message d'erreur ne partage son bloc avec un bouton rouge. */

.spm-alerte {
  display: flex;
  align-items: flex-start;
  gap: var(--e12);
  padding: var(--erreur-bloc-marge-i);
  border-radius: var(--rayon);
  font-size: var(--t-texte);
  line-height: var(--interligne-texte);
}

.spm-alerte--erreur {
  background-color: var(--erreur-bloc-fond);
  color: var(--erreur-bloc-texte);
  border-left: var(--erreur-bloc-filet-gauche);
}

.spm-alerte--succes {
  background-color: var(--succes-surface);
  color: var(--succes);
  border: var(--filet-epaisseur) solid currentColor;
}

.spm-alerte--attention {
  background-color: var(--attention-surface);
  color: var(--attention);
  border: var(--filet-epaisseur) solid currentColor;
}

.spm-alerte__texte { max-width: var(--mesure); }

/* Le pictogramme d'alerte est au trait, jamais plein, jamais dans une
   pastille. Le TRIANGLE est réservé à l'erreur et n'apparaît nulle part
   ailleurs dans le jeu (fondations.md §5.4). */
.spm-alerte__icone {
  flex: none;
  width: var(--icone-taille);
  height: var(--icone-taille);
}

/* =============================================== 7. PICTOGRAMMES ======== */

/* Trait = taille ÷ 16. 24 px est la taille de référence, 1,5 px le trait.
   Aucun remplissage, `currentColor`, angles vifs. */
.spm-icone {
  width: var(--icone-taille);
  height: var(--icone-taille);
  fill: none;
  stroke: var(--icone-couleur);
  stroke-width: var(--icone-trait);
  stroke-linecap: butt;
  stroke-linejoin: miter;
}

/* ==================================== 8. EN-TÊTE ET PIED DE PAGE ======== */

/* ⛔ Jamais de fond rouge en en-tête ni en pied de page permanent : il
   épuiserait la règle du dixième dès le premier pixel et transformerait tout
   le site en alerte (fondations.md §1.5). L'en-tête est sur `--fond`. */

.spm-entete {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--e24);
  padding-block: var(--e16);
  background-color: var(--fond);
  border-bottom: var(--filet-epaisseur) solid var(--filet);
}

/* Le logo garde sa zone de protection : rien n'entre dans la bande qui
   ceinture le verrouillage. Minimum écran du verrouillage horizontal :
   200 px de large (FICHE-LOGO §5). */
.spm-entete__logo {
  display: block;
  padding: var(--e8);
  text-decoration: none;
}

.spm-entete__logo img {
  width: clamp(200px, 18vw, 260px);
  height: auto;
}

/* Navigation sans JavaScript : une liste de liens qui passe à la ligne.
   Aucun menu qui a besoin d'un script pour s'ouvrir. */
.spm-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--e24);
  list-style: none;
  margin: 0;
  padding: 0;
}

.spm-nav li { margin: 0; }

.spm-nav a {
  color: var(--texte);
  font-weight: var(--graisse-medium);
  text-decoration: none;
  padding-block: var(--e8);
}

.spm-nav a:hover,
.spm-nav a[aria-current] {
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: .3em;
  text-decoration-color: var(--marque);
}

.spm-pied {
  padding-block: var(--e64);
  background-color: var(--surface);
  border-top: var(--filet-epaisseur) solid var(--filet);
  color: var(--texte-secondaire);
}

.spm-pied a { color: var(--marque-texte); }

/* Coordonnées légales, IDE, TVA, mentions : la dernière ligne du pied. */
.spm-pied__legal {
  margin-top: var(--e32);
  padding-top: var(--e16);
  border-top: var(--filet-epaisseur) solid var(--filet);
  font-size: var(--t-micro);
  line-height: 1.4;
  color: var(--texte-faible);
}

/* ===================================================== 9. PHOTO ========= */

/* Pas de cadre, pas de filet, pas d'ombre, pas de coin arrondi. Au plus UN
   coin coupé à la pente d'ouverture, le même pour toute la page, et au plus
   deux images coupées par page (langage-graphique.md §4).
   La coupe est `.coupe-rabat` (tokens.css §4) :
       <figure class="spm-photo">
         <img class="coupe-rabat" src="…" alt="…" width="…" height="…">
         <figcaption>Chargement rue de Carouge, 19.09.2026</figcaption>
       </figure> */

.spm-photo { margin: 0; }

.spm-photo img {
  width: 100%;
  height: auto;
  border-radius: var(--rayon);
  box-shadow: var(--ombre);
}

/* Garde-fou : une image coupée ne prend jamais de rayon ni d'ombre. */
.spm-photo img.coupe-rabat {
  border-radius: 0;
  box-shadow: var(--ombre);
}

.spm-photo figcaption {
  margin-top: var(--e8);
  font-size: var(--t-petit);
  line-height: var(--interligne-petit);
  color: var(--texte-faible);
  max-width: var(--mesure);
}

/* ============================== 10. LE BLOC DE MARQUE, ET LE PLI ======== */

/* UN SEUL grand aplat rouge par page, et il ne coexiste pas avec un bouton
   principal à pleine force. Il porte trois choses au maximum : un titre
   condensé blanc, une phrase, une action (langage-graphique.md §6.3).
   Interdit : en en-tête permanent, en pied permanent, en fond de formulaire,
   derrière une photographie.

   Le pli est `.pli` (tokens.css §4) : le bord supérieur descend UNE fois,
   vers la droite, à la pente d'ouverture. Un seul pli par page, et il marque
   l'entrée dans ce bloc :
       <section class="spm-bloc-marque pli"> … </section> */

.spm-bloc-marque {
  padding-block: var(--e80);
  background-color: var(--marque);
  color: var(--sur-marque);
}

.spm-bloc-marque h1,
.spm-bloc-marque h2,
.spm-bloc-marque h3,
.spm-bloc-marque p { color: inherit; }

.spm-bloc-marque a { color: var(--sur-marque); }

/* Dans le bloc rouge, l'action est un bouton cerné de blanc : un second
   aplat rouge n'y serait plus lisible, et un bouton blanc plein y ferait un
   deuxième aplat. */
.spm-bloc-marque .spm-btn--secondaire {
  color: var(--sur-marque);
  border-color: var(--sur-marque);
  background-color: transparent;
}

.spm-bloc-marque .spm-btn--secondaire:hover {
  background-color: var(--sur-marque);
  color: var(--marque);
}

/* Le pli et la coupe ne se combinent jamais avec un rayon. */
.spm-bloc-marque.pli { border-radius: 0; }

/* =============================================== 11. SEMIS DE RABATS ==== */

/* Papier peint discret : exactement 4 rabats par carreau de 192 px, un par
   orientation du logo, jamais une cinquième. Aucune croix n'y est dessinée.
   Le fichier `assets/motifs/semis-rabats.svg` sert de MASQUE : la forme vient
   de lui, la couleur d'un jeton. Il n'y a donc aucune couleur hors jetons, et
   aucune requête externe — le chemin est relatif.

   Une zone par page au maximum, jamais sous du texte courant, jamais en fond
   de bouton, jamais derrière une photo, jamais en filigrane sur un devis,
   jamais en dégradé, jamais animé (langage-graphique.md §3.1). */

.spm-semis {
  position: relative;
  isolation: isolate;
}

/* ⚠️ LE MASQUE N'EST PAS UN ACQUIS, ET IL EST DISCRET PAR CONSTRUCTION.
   Regardé dans un navigateur le 19.09.2026 (Chrome, `_outils/captures.py`,
   puis comptage des pixels de la bande rendue) :
     masque effectivement appliqué — `mask-image` résolu en
     assets/motifs/semis-rabats.svg, `mask-size` 192 px, `mask-repeat` repeat ;
     couverture réelle **3,48 %** de la surface (3 120 px teintés sur 89 552),
     soit quatre rabats d'environ 18 × 18 px par carreau de 192 px ;
     contraste du semis clair sur le fond de page : ~1,04:1.
   Autrement dit : il REND, et il se voit à peine — c'est voulu, un semis de
   fond n'est pas un motif de papier peint. Ne pas le « réparer » en le
   fonçant : il passerait devant le texte, ce que la règle interdit.

   Le repli ci-dessous existe parce qu'un masque n'est pas un acquis : un
   navigateur qui ne connaît pas `mask-image` afficherait un APLAT PLEIN du
   voile sur toute la zone — c'est-à-dire un grand rectangle teinté là où le
   système promet quatre petits rabats. Le défaut serait pire que l'absence.
   On ne peint donc le ::before QUE si le masque est supporté.

   ⚠️ La couleur du semis est le VOILE DE MARQUE, pas une surface neutre —
   corrigé le 20.09.2026. Elle valait --surface-enfoncee, c'est-à-dire
   --neutre-100 #F1EFEC : relevé au pixel sur la capture du 404, la bande
   rendait rgb(239,240,235), un gris légèrement VERT. Le livret pose
   --marque-voile (#FFF2EF en clair, #690401 en sombre, fondations.md §214 et
   README §294) : « volontairement presque rien », mais rouge. La seule zone
   de semis du site portait donc la couleur d'une autre marque que la
   sienne. La discrétion ne change pas : le voile est à 1,09:1 du blanc,
   l'ancienne valeur était à ~1,04:1, et la couverture reste 3,48 %. */
.spm-semis::before {
  content: none;                       /* repli : aucun aplat par défaut */
}

/* On teste la PROPRIETE, pas une URL : ecrire une url() d'essai ici
   fabriquait un lien mort que le controle du paquet tentait de resoudre. */
@supports ((mask-image: none) or (-webkit-mask-image: none)) {
  .spm-semis::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: var(--z-decor);
    pointer-events: none;
    background-color: var(--marque-voile);
    -webkit-mask-image: url("../assets/motifs/semis-rabats.svg");
    mask-image: url("../assets/motifs/semis-rabats.svg");
    -webkit-mask-size: var(--motif-carreau) var(--motif-carreau);
    mask-size: var(--motif-carreau) var(--motif-carreau);
    -webkit-mask-repeat: repeat;
    mask-repeat: repeat;
  }
}

.spm-semis > * {
  position: relative;
  z-index: var(--z-contenu);
}

/* Variante de marque : le voile rouge clair. */
.spm-semis--marque::before { background-color: var(--marque-voile); }

/* =============================================== 12. FENÊTRE MODALE ===== */

/* Le voile n'est pas une ombre : il se pose sur la page, pas sous la fenêtre. */
.spm-fenetre {
  padding: var(--e32);
  border: var(--filet-epaisseur) solid var(--filet-fort);
  border-radius: var(--rayon);
  background-color: var(--fond);
  color: var(--texte);
  box-shadow: var(--ombre);
  max-width: var(--mesure-etroite);
}

.spm-fenetre::backdrop { background-color: var(--voile); }
