/* Devis Vocal — interface artisan.

   Refonte complète. Ce qui change par rapport à la feuille précédente, et pourquoi :

   - Toutes les valeurs viennent de `tokens.css`. La feuille d'avant recopiait ses
     tailles à la main et les redéclarait sous trois requêtes média ; le barème est
     désormais fluide (`clamp`), donc écrit une fois.
   - Une ossature persistante remplace six écrans qui se redécouvraient chacun.
   - Le texte a été assombri d'un cran : le gris des libellés donnait 2,91:1 sur le
     fond papier, sous le seuil AA. La hiérarchie se fait maintenant par la taille,
     la graisse et l'interlettrage — pas en délavant.
   - Chaque surface touchable fait 48px au moins, et chaque élément focalisable a
     un anneau visible. Rien de tout cela n'existait.

   Chargée après tokens.css et fonts.css. Le document A4 vit dans templates/pdf.css.
   Ordre de lecture : socle, ossature, atomes, écrans, adaptation, mouvement. */


/* ======================================================================== */
/* Socle                                                                    */
/* ======================================================================== */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  /* L'ossature est collante : tout défilement provoqué par le navigateur — un champ
     qui prend le focus, le lien d'évitement, une ancre — amènerait sa cible sous la
     barre. Observé sur le formulaire d'inscription, où le titre passait dessous.
     Une marge de défilement de la hauteur de la barre, et la cible arrive dessous. */
  scroll-padding-top: calc(var(--dv-entete-h) + var(--dv-s-4));
}

/* Pas de tirer-pour-rafraîchir : sur Chrome Android comme sur Safari, remonter un peu
   trop vite en haut d'un devis rechargeait la page — et le devis, qui ne vit qu'ici,
   disparaissait. Posé sur les deux : selon le navigateur, c'est l'un ou l'autre qui
   porte le défilement du document. */
html, body { overscroll-behavior-y: contain; }

/* `hidden` doit cacher, sans exception.

   Le navigateur applique `[hidden] { display: none }` depuis sa propre feuille,
   qui perd contre n'importe quelle règle d'auteur. Or ce produit pilote sa moitié
   de l'interface par cet attribut — barre de compte, alerte du template, bouton de
   reprise, attente de l'aperçu — et chacune de ces surfaces porte par ailleurs un
   `display: flex`. Sans cette ligne, toutes restaient visibles en permanence :
   l'attente de l'aperçu poussait la feuille A4 à mille pixels sous le cadre, et
   la barre de compte s'affichait sans que personne soit connecté. */
[hidden] { display: none !important; }

body {
  margin: 0;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  background: var(--dv-bg);
  color: var(--dv-ink);
  font-family: var(--dv-sans);
  font-size: var(--dv-t-body);
  line-height: var(--dv-lh-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* Les chiffres du produit sont tabulaires partout : un total qui change de
     largeur en changeant de valeur fait sauter la colonne. */
  font-variant-numeric: tabular-nums;
}

/* L'ossature occupe sa hauteur ; le reste de la fenêtre revient aux écrans, qui
   s'y centrent. Un `calc(100svh - ...)` par écran, comme avant, obligeait à
   soustraire aussi le bandeau — donc à connaître sa hauteur en CSS. */
main {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

h1, h2, h3 { margin: 0; font-family: var(--dv-serif); font-weight: 600; }
p { margin: 0; }

a { color: var(--dv-accent); text-underline-offset: .18em; }
a:hover { color: var(--dv-accent-dark); }

img, svg, iframe { display: block; max-width: 100%; }

/* Le jeu de symboles n'est pas un élément de page : il définit, il n'affiche pas. */
.jeu-icones { position: absolute; width: 0; height: 0; overflow: hidden; }

/* Toute icône du produit fait 20px et suit la couleur de son texte. Une seule
   taille par défaut : les écarts sont déclarés là où ils ont une raison. */
svg use { color: inherit; }
.btn svg, .compte__lien svg, .menu__lien svg, .menu__bascule svg,
.template__pied svg, .paste-toggle svg {
  width: 20px; height: 20px; flex: none;
}

/* ---- accessibilité du clavier ------------------------------------------ */
/* La feuille précédente n'avait aucun état de focalisation hors du formulaire de
   connexion : au clavier, on traversait le produit à l'aveugle. Un seul anneau,
   posé une fois, sur tout ce qui se focalise. `:focus-visible` et non `:focus` :
   le doigt ne doit pas laisser d'anneau derrière lui. */

:focus-visible {
  outline: 2px solid var(--dv-accent);
  outline-offset: 2px;
  border-radius: var(--dv-radius);
}

/* Le saut vers le contenu. Hors écran tant qu'on ne l'atteint pas au clavier. */
.saut {
  position: fixed; top: var(--dv-s-3); left: var(--dv-s-3); z-index: 100;
  transform: translateY(calc(-100% - var(--dv-s-11)));
  padding: var(--dv-s-4) var(--dv-s-6);
  background: var(--dv-ink); color: var(--dv-paper);
  font-size: var(--dv-t-sm); font-weight: 500;
  border-radius: var(--dv-radius);
  text-decoration: none;
  transition: transform var(--dv-duree) var(--dv-sortie);
}
.saut:focus { transform: translateY(0); color: var(--dv-paper); }

/* Seul l'écran actif existe. On masque ce qui ne l'est pas et on laisse chaque
   écran choisir son mode d'affichage — sans quoi `.accueil { display: flex }`
   l'emporterait sur un `display: none` de même spécificité. */
.screen:not(.is-active) { display: none; }

/* L'écran actif prend la place restante et s'y centre. Le padding vertical est
   celui de la gouttière, doublé : un écran collé sous l'ossature paraît tombé. */
.screen {
  flex: 1;
  display: flex;
  justify-content: center;
  padding: clamp(var(--dv-s-9), 6vh, var(--dv-s-12)) var(--dv-marge) var(--dv-s-12);
  /* L'entrée d'écran. Deux propriétés seulement, toutes deux composées par le GPU :
     une opacité et une translation. Animer une hauteur ferait ramer un téléphone. */
  animation: entree-ecran var(--dv-duree-lente) var(--dv-sortie) both;
}
@keyframes entree-ecran {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

/* Le temps de savoir qui est connecté, on ne sait pas quel écran est le bon.
   `visibility` plutôt que `display` : la page garde sa hauteur, donc rien ne
   saute au moment où elle réapparaît. */
body.compte-inconnu .screen { visibility: hidden; }


/* ======================================================================== */
/* Ossature                                                                 */
/* ======================================================================== */

/* ---- bandeau des substitutions silencieuses ---------------------------- */
/* Trois façons de montrer autre chose que ce qu'on croit montrer : un chiffrage
   rejoué depuis une fixture, un moteur de secours, un devis sorti sur la mise en
   page livrée. Toutes les trois se disent ici, dans la teinte d'accent — la seule
   surface du produit qui a le droit d'être un aplat. */

.bandeau {
  background: var(--dv-accent-tint);
  border-bottom: 1px solid var(--dv-accent-edge);
  color: var(--dv-accent);
}
.bandeau__inner {
  max-width: var(--dv-mesure-xl);
  margin: 0 auto;
  padding: var(--dv-s-4) var(--dv-marge);
  display: flex; align-items: flex-start; gap: var(--dv-s-4);
  font-family: var(--dv-mono);
  font-size: var(--dv-t-xs);
  line-height: var(--dv-lh-body);
}
.bandeau__icone { width: 18px; height: 18px; flex: none; margin-top: 2px; }
.bandeau strong { font-weight: 500; }
.bandeau code { font-family: var(--dv-mono); background: rgba(138, 75, 37, .09); padding: 1px 5px; border-radius: 3px; }

/* ---- la barre persistante ---------------------------------------------- */
/* Collante, et volontairement peu dense : elle doit se laisser oublier pendant
   qu'on relit un devis, et se retrouver sans chercher quand on veut revenir. */

.entete {
  position: sticky; top: 0; z-index: 20;
  background: color-mix(in srgb, var(--dv-bg) 88%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--dv-rule);
}
.entete__inner {
  max-width: var(--dv-mesure-xl);
  margin: 0 auto;
  min-height: var(--dv-entete-h);
  padding: 0 var(--dv-marge);
  display: flex; align-items: center; gap: var(--dv-s-4);
}

.marque {
  display: flex; align-items: center;
  min-height: var(--dv-cible);
  padding: 0 var(--dv-s-2) 0 0;
  margin-left: calc(var(--dv-s-2) * -1);
  padding-left: var(--dv-s-2);
  border: none; background: none; cursor: pointer;
  font-family: var(--dv-serif);
  font-size: var(--dv-t-h3);
  font-weight: 600;
  letter-spacing: var(--dv-track-tight);
  color: var(--dv-ink);
  transition: color var(--dv-duree-vite) var(--dv-sortie);
}
.marque:hover { color: var(--dv-accent); }

/* Le fil : où l'on se trouve, quand ce n'est pas l'accueil. Un mot, pas une
   arborescence — le produit n'a qu'un niveau. */
.entete__fil {
  font-family: var(--dv-mono);
  font-size: var(--dv-t-label);
  letter-spacing: var(--dv-track-label);
  text-transform: uppercase;
  color: var(--dv-faint);
  padding-left: var(--dv-s-4);
  border-left: 1px solid var(--dv-rule);
  white-space: nowrap;
}

/* ---- le menu latéral du compte ----------------------------------------- */
/* Discret par construction : le sujet de cette page est le devis. Une colonne à
   gauche, qui dit en haut qui est connecté et range en bas ce qu'on peut y faire.

   Deux comportements, selon la place :

   - à partir de 1100px, ouvert, il pousse la page de sa largeur — rien ne passe
     dessous, et il peut rester ouvert pendant qu'on travaille ;
   - en dessous, il glisse par-dessus la page derrière un voile, et se referme dès
     qu'on a choisi.

   1100 et pas moins : la page poussée garde ainsi plus de 820px, donc aucun des
   seuils de la section « Adaptation » ne se retrouve faussé — sauf celui des deux
   colonnes de la relecture, redit plus bas pour ce cas. */

.menu {
  position: fixed; top: 0; bottom: 0; left: 0; z-index: 30;
  width: var(--dv-menu-l);
  max-width: 86vw;
  display: flex; flex-direction: column;
  background: var(--dv-bg-alt);
  border-right: 1px solid var(--dv-rule);
  overflow-y: auto;
  /* Fermé, il est hors écran et hors du parcours au clavier : `visibility` retire
     ses liens de la tabulation, ce qu'une simple translation ne ferait pas. */
  transform: translateX(-100%);
  visibility: hidden;
}
body.menu-ouvert .menu { transform: none; visibility: visible; }

.menu__tete {
  flex: none;
  display: flex; align-items: center; justify-content: space-between;
  min-height: var(--dv-entete-h);
  padding: 0 var(--dv-s-3) 0 var(--dv-s-7);
  border-bottom: 1px solid var(--dv-rule);
}

/* L'icône qui masque et rouvre le menu. La même des deux côtés : c'est un seul
   geste, il n'a pas à changer de dessin selon l'état. */
.menu__bascule {
  flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--dv-cible); height: var(--dv-cible);
  border: none; background: none; cursor: pointer;
  color: var(--dv-muted);
  border-radius: var(--dv-radius);
  transition: color var(--dv-duree-vite) var(--dv-sortie),
              background var(--dv-duree-vite) var(--dv-sortie);
}
.menu__bascule:hover { color: var(--dv-accent); background: var(--dv-accent-tint); }
/* Dans la barre, l'icône s'aligne sur la gouttière : c'est son dessin qui doit
   tomber sous le texte de la page, pas le bord de sa cible de 48px. */
.entete .menu__bascule { margin-left: calc(var(--dv-s-5) * -1); }

.menu__qui { padding: var(--dv-s-8) var(--dv-s-7) var(--dv-s-7); }
.menu__nom {
  font-family: var(--dv-serif);
  font-size: var(--dv-t-h3);
  font-weight: 600;
  line-height: var(--dv-lh-snug);
  letter-spacing: var(--dv-track-tight);
  color: var(--dv-ink);
  overflow-wrap: anywhere;
}
.menu__role {
  margin-top: var(--dv-s-1);
  font-size: var(--dv-t-sm);
  color: var(--dv-muted);
}
.menu__email {
  margin-top: var(--dv-s-3);
  font-family: var(--dv-mono);
  font-size: var(--dv-t-xs);
  line-height: var(--dv-lh-snug);
  color: var(--dv-faint);
  overflow-wrap: anywhere;
}
.menu__entreprise {
  margin-top: var(--dv-s-7);
  padding-top: var(--dv-s-6);
  border-top: 1px solid var(--dv-rule);
}
.menu__entreprise-nom {
  margin-top: var(--dv-s-2);
  font-weight: 500;
  line-height: var(--dv-lh-snug);
  color: var(--dv-ink-soft);
  overflow-wrap: anywhere;
}

/* Ce qui pousse les liens tout en bas, quelle que soit la hauteur de la fenêtre. */
.menu__espace { flex: 1; min-height: var(--dv-s-8); }

.menu__liens {
  flex: none;
  display: flex; flex-direction: column;
  padding: var(--dv-s-4) var(--dv-s-4) max(var(--dv-s-4), env(safe-area-inset-bottom));
  border-top: 1px solid var(--dv-rule);
}
.menu__lien {
  display: flex; align-items: center; gap: var(--dv-s-4);
  width: 100%;
  min-height: var(--dv-cible);
  padding: 0 var(--dv-s-4);
  border: none; background: none; cursor: pointer;
  font-family: var(--dv-sans);
  font-size: var(--dv-t-body);
  text-align: left;
  color: var(--dv-text);
  border-radius: var(--dv-radius);
  transition: color var(--dv-duree-vite) var(--dv-sortie),
              background var(--dv-duree-vite) var(--dv-sortie);
}
.menu__lien:hover { color: var(--dv-accent); background: var(--dv-accent-tint); }

.menu-voile {
  position: fixed; inset: 0; z-index: 25;
  background: var(--dv-voile);
  opacity: 0;
  visibility: hidden;
}

@media (min-width: 1100px) {
  body.menu-ouvert { padding-left: var(--dv-menu-l); }
}
@media (max-width: 1099px) {
  body.menu-ouvert .menu { box-shadow: var(--dv-ombre-3); }
  body.menu-ouvert .menu-voile { opacity: 1; visibility: visible; }
}

/* Le glissement. Posé par `compte.js` après le premier affichage seulement : au
   chargement, le menu doit être là, pas arriver — sans quoi toute la page se
   décalerait sous les yeux à chaque ouverture de l'application. */
body.menu-anime { transition: padding-left var(--dv-duree) var(--dv-sortie); }
body.menu-anime .menu {
  transition: transform var(--dv-duree) var(--dv-entree),
              visibility 0s linear var(--dv-duree);
}
body.menu-anime.menu-ouvert .menu {
  transition: transform var(--dv-duree) var(--dv-sortie), visibility 0s;
}
body.menu-anime .menu-voile {
  transition: opacity var(--dv-duree) var(--dv-sortie),
              visibility 0s linear var(--dv-duree);
}
body.menu-anime.menu-ouvert .menu-voile {
  transition: opacity var(--dv-duree) var(--dv-sortie), visibility 0s;
}

/* Le lien discret, hors du menu : « Revenir au template livré ». */
.compte__lien {
  display: inline-flex; align-items: center; gap: var(--dv-s-2);
  min-height: var(--dv-cible);
  padding: 0 var(--dv-s-2);
  border: none; background: none; cursor: pointer;
  font-family: var(--dv-sans);
  font-size: var(--dv-t-sm);
  color: var(--dv-muted);
  transition: color var(--dv-duree-vite) var(--dv-sortie);
}
.compte__lien:hover { color: var(--dv-accent); }


/* ======================================================================== */
/* Atomes                                                                   */
/* ======================================================================== */

/* Le libellé en capitales mono. Il ne se distingue plus par sa pâleur — il est
   lisible — mais par sa taille, son interlettrage et sa graisse. */
.label {
  font-family: var(--dv-mono);
  font-size: var(--dv-t-label);
  font-weight: 500;
  letter-spacing: var(--dv-track-label);
  text-transform: uppercase;
  color: var(--dv-faint);
  line-height: 1.4;
}
.label--accent { color: var(--dv-accent); }
.label--danger { color: var(--dv-danger); }

.num { font-family: var(--dv-mono); }   /* tout chiffre est en mono, toujours */

/* ---- boutons ------------------------------------------------------------ */
/* Une seule fabrique. La hauteur minimale est la cible tactile, pas une valeur
   d'esthétique : c'est un pouce, sur un chantier, éventuellement ganté.
   L'enfoncement se joue sur `transform` — donc sans déplacer ce qui est autour. */

.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--dv-s-3);
  min-height: var(--dv-cible);
  padding: var(--dv-s-4) var(--dv-s-7);
  border: 1px solid transparent;
  border-radius: var(--dv-radius);
  background: var(--dv-ink);
  color: var(--dv-paper);
  font-family: var(--dv-sans);
  font-size: var(--dv-t-body);
  font-weight: 500;
  line-height: 1.2;
  text-align: center;
  cursor: pointer;
  touch-action: manipulation;   /* supprime le délai de 300 ms sur mobile */
  transition:
    background var(--dv-duree-vite) var(--dv-sortie),
    border-color var(--dv-duree-vite) var(--dv-sortie),
    color var(--dv-duree-vite) var(--dv-sortie),
    transform var(--dv-duree-vite) var(--dv-ressort),
    box-shadow var(--dv-duree-vite) var(--dv-sortie);
}
.btn--large { padding: var(--dv-s-4) var(--dv-s-8); }

.btn:hover { background: #2a2823; }
.btn:active { transform: scale(.985); }

.btn--ghost {
  background: var(--dv-paper);
  color: var(--dv-text);
  border-color: var(--dv-border-input);
  box-shadow: var(--dv-ombre-1);
}
.btn--ghost:hover { background: #fff; border-color: var(--dv-rule-strong); color: var(--dv-ink); }

/* Une action qui détruit quelque chose. Se pose sur `.btn--ghost` : c'est le texte
   qui est rouge, pas l'aplat — un bouton plein rouge crierait plus fort que
   l'action principale à côté de lui. */
.btn--danger { color: var(--dv-danger); }
.btn--danger:hover {
  background: var(--dv-danger-tint);
  border-color: var(--dv-danger-edge);
  color: var(--dv-danger);
}

/* L'état désactivé se lit à trois choses à la fois, jamais à la seule opacité :
   il pâlit, il perd le curseur de main, et il ne répond plus au doigt. */
.btn:disabled {
  opacity: .42;
  cursor: not-allowed;
  transform: none;
  pointer-events: none;
}

.btn--google {
  background: var(--dv-paper);
  color: var(--dv-ink);
  border-color: var(--dv-border-input);
  box-shadow: var(--dv-ombre-1);
  width: 100%;
}
.btn--google:hover { border-color: var(--dv-rule-strong); background: #fff; }
.btn__g {
  font-family: var(--dv-serif);
  font-size: 17px; font-weight: 700;
  color: var(--dv-accent);
}

/* ---- champs ------------------------------------------------------------- */
/* Étiquette visible au-dessus, aide persistante en dessous. Le texte de
   substitution ne sert plus d'étiquette nulle part : il s'efface à la frappe. */

.champ { display: flex; flex-direction: column; gap: var(--dv-s-2); }
.champ__label {
  font-size: var(--dv-t-sm);
  font-weight: 500;
  color: var(--dv-text);
}
.champ__aide {
  font-size: var(--dv-t-xs);
  color: var(--dv-faint);
}
.champ input,
.champ select,
.champ textarea {
  width: 100%;
  min-height: var(--dv-cible);
  padding: var(--dv-s-4) var(--dv-s-5);
  border: 1px solid var(--dv-border-input);
  border-radius: var(--dv-radius);
  background: var(--dv-paper);
  color: var(--dv-ink);
  font-family: var(--dv-sans);
  font-size: var(--dv-t-body);   /* 16px : en dessous, iOS zoome à la mise au point */
  line-height: var(--dv-lh-body);
  transition: border-color var(--dv-duree-vite) var(--dv-sortie),
              box-shadow var(--dv-duree-vite) var(--dv-sortie);
}
.champ textarea { min-height: 140px; resize: vertical; }
.champ input::placeholder,
.champ textarea::placeholder { color: var(--dv-faint); }
.champ input:focus,
.champ select:focus,
.champ textarea:focus {
  outline: none;
  border-color: var(--dv-accent);
  box-shadow: 0 0 0 3px var(--dv-accent-tint);
}

/* Le `select`. Le chevron du système est dessiné différemment sur chaque
   plateforme et ne suit aucune de nos couleurs : on le remplace par le même
   trait que le reste, en SVG inline — donc sans fichier ni requête. */
.champ select {
  appearance: none;
  padding-right: var(--dv-s-11);
  cursor: pointer;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' fill='none' stroke='%235f5a52' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m5 8 5 5 5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--dv-s-5) center;
}
/* Tant que rien n'est choisi, l'invite se lit comme un texte de substitution et
   non comme une réponse déjà donnée. */
.champ select:has(option[value=""]:checked) { color: var(--dv-faint); }

/* ---- zones de dépôt ------------------------------------------------------ */

.dropzone {
  border: 1.5px dashed var(--dv-rule-strong);
  border-radius: var(--dv-radius-lg);
  background: var(--dv-paper);
  padding: var(--dv-s-11) var(--dv-s-9);
  text-align: center;
  cursor: pointer;
  touch-action: manipulation;
  transition: border-color var(--dv-duree) var(--dv-sortie),
              background var(--dv-duree) var(--dv-sortie),
              box-shadow var(--dv-duree) var(--dv-sortie),
              transform var(--dv-duree-vite) var(--dv-ressort);
}
.dropzone--mince { padding: var(--dv-s-9) var(--dv-s-7); }
.dropzone.is-over {
  border-color: var(--dv-accent);
  border-style: solid;
  background: #fff;
  box-shadow: var(--dv-ombre-2);
}
.dropzone__title {
  font-family: var(--dv-serif);
  font-size: var(--dv-t-h3);
  font-weight: 600;
  line-height: var(--dv-lh-snug);
  margin-bottom: var(--dv-s-2);
}
.dropzone__hint { font-size: var(--dv-t-sm); color: var(--dv-muted); }

/* ---- en-tête de section -------------------------------------------------- */

.section-head {
  display: flex; align-items: center; gap: var(--dv-s-4);
  margin-bottom: var(--dv-s-1);
}
.section-head .rule { height: 1px; flex: 1; background: var(--dv-rule); }
.section-head .count { font-size: var(--dv-t-xs); color: var(--dv-muted); }


/* ======================================================================== */
/* 0. Connexion                                                             */
/* ======================================================================== */

.connexion { align-items: center; }
.connexion__inner { width: 100%; max-width: var(--dv-mesure-etroite); }

.connexion__titre {
  font-size: var(--dv-t-h1);
  line-height: var(--dv-lh-tight);
  letter-spacing: var(--dv-track-tight);
  text-wrap: balance;
}
.connexion__baseline {
  font-size: var(--dv-t-lead);
  color: var(--dv-muted);
  margin: var(--dv-s-4) 0 var(--dv-s-10);
  text-wrap: pretty;
}

.connexion__ou {
  display: flex; align-items: center; gap: var(--dv-s-4);
  margin: var(--dv-s-7) 0;
  font-size: var(--dv-t-xs);
  color: var(--dv-faint);
}
.connexion__ou::before, .connexion__ou::after {
  content: ""; height: 1px; flex: 1; background: var(--dv-rule);
}

.connexion__form { display: flex; flex-direction: column; gap: var(--dv-s-5); }
.connexion__form .btn { width: 100%; }
.connexion__form .btn--ghost { margin-top: calc(var(--dv-s-2) * -1); }

/* La sortie sans compte. Un lien plutôt qu'un troisième bouton plein : elle doit se
   trouver sans faire concurrence à la connexion. */
.connexion__passer {
  display: flex; align-items: center; justify-content: center; gap: var(--dv-s-3);
  width: 100%;
  min-height: var(--dv-cible);
  margin-top: var(--dv-s-8);
  padding: 0;
  border: none; background: none; cursor: pointer;
  font-family: var(--dv-sans);
  font-size: var(--dv-t-sm);
  font-weight: 500;
  color: var(--dv-muted);
  transition: color var(--dv-duree-vite) var(--dv-sortie);
}
.connexion__passer:hover { color: var(--dv-accent); }
.connexion__passer svg { width: 18px; height: 18px; transform: scaleX(-1); }

.connexion__erreur {
  margin-top: var(--dv-s-6);
  padding: var(--dv-s-4) var(--dv-s-5);
  background: var(--dv-danger-tint);
  border: 1px solid var(--dv-danger-edge);
  border-radius: var(--dv-radius);
  color: var(--dv-danger);
  font-size: var(--dv-t-sm);
}
/* Le pendant de l'erreur, pour ce qui s'est bien passé : l'e-mail de
   réinitialisation est parti. */
.connexion__info {
  margin-top: var(--dv-s-6);
  padding: var(--dv-s-4) var(--dv-s-5);
  background: var(--dv-accent-tint);
  border: 1px solid var(--dv-accent-edge);
  border-radius: var(--dv-radius);
  color: var(--dv-accent);
  font-size: var(--dv-t-sm);
  line-height: var(--dv-lh-body);
}

/* « Mot de passe oublié ? », sous le champ, en face de son aide. Un lien et non un
   bouton plein : c'est la sortie de secours, pas le chemin. La marge négative rend
   à la ligne la hauteur que la cible tactile lui prend. */
.champ__pied {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--dv-s-4);
  margin-top: calc(var(--dv-s-2) * -1);
}
.connexion__oubli {
  display: inline-flex; align-items: center;
  min-height: var(--dv-cible);
  padding: 0;
  border: none; background: none; cursor: pointer;
  font-family: var(--dv-sans);
  font-size: var(--dv-t-sm);
  color: var(--dv-muted);
  text-decoration: underline;
  text-underline-offset: 3px;
  transition: color var(--dv-duree-vite) var(--dv-sortie);
}
.connexion__oubli:hover { color: var(--dv-accent); }
.connexion__oubli:disabled { cursor: default; opacity: .6; }


/* ======================================================================== */
/* 0 bis. Profil                                                            */
/*                                                                          */
/* La dernière étape de l'inscription. Même mesure et même construction que */
/* la connexion, à laquelle elle succède : l'artisan doit sentir qu'il finit */
/* quelque chose, pas qu'il recommence ailleurs.                            */
/* ======================================================================== */

.profil { align-items: center; }
.profil__inner { width: 100%; max-width: var(--dv-mesure-etroite); }

.profil__titre {
  margin-top: var(--dv-s-4);
  font-size: var(--dv-t-h1);
  line-height: var(--dv-lh-tight);
  letter-spacing: var(--dv-track-tight);
  text-wrap: balance;
}
.profil__baseline {
  font-size: var(--dv-t-lead);
  line-height: var(--dv-lh-body);
  color: var(--dv-muted);
  margin: var(--dv-s-5) 0 var(--dv-s-10);
  text-wrap: pretty;
}

.profil__form { display: flex; flex-direction: column; gap: var(--dv-s-5); }
.profil__form .btn { width: 100%; margin-top: var(--dv-s-3); }

/* Prénom et nom sur une ligne. Ils passent l'un sous l'autre sur les écrans
   étroits, où deux champs côte à côte ne laissent plus lire ce qu'on tape. */
.profil__paire { display: grid; grid-template-columns: 1fr 1fr; gap: var(--dv-s-4); }
@media (max-width: 420px) {
  .profil__paire { grid-template-columns: 1fr; }
}

/* Le champ signalé en erreur. La couleur ne porte pas seule l'information : le
   message sous le formulaire dit lequel manque, et le focus y est déplacé. */
.champ input[aria-invalid="true"],
.champ select[aria-invalid="true"] {
  border-color: var(--dv-danger);
  box-shadow: 0 0 0 3px var(--dv-danger-tint);
}


/* ======================================================================== */
/* 1. Accueil                                                               */
/* ======================================================================== */

.accueil { align-items: center; }
.accueil__inner { width: 100%; max-width: var(--dv-mesure); }

.accueil__hero { margin-bottom: var(--dv-s-10); }
.accueil__titre {
  font-size: var(--dv-t-h1);
  line-height: var(--dv-lh-tight);
  letter-spacing: var(--dv-track-tight);
  text-wrap: balance;
}
.accueil__baseline {
  font-size: var(--dv-t-lead);
  color: var(--dv-muted);
  margin-top: var(--dv-s-5);
  max-width: 46ch;
  text-wrap: pretty;
}

/* ---- la carte de dictée -------------------------------------------------- */
/* L'action principale du produit. Elle porte la boîte de `.dropzone`, mais pas son
   tireté : les pointillés disent « déposez un fichier », et ici on parle. Un filet
   plein, et le micro en disque brique — le seul aplat brique de l'app. La brique
   pour parler, le noir pour agir. Trois états : au repos, en enregistrement, en
   réécoute. */

.dictee { user-select: none; position: relative; border-style: solid; border-color: var(--dv-rule); }
.dictee:hover { border-color: var(--dv-accent); box-shadow: var(--dv-ombre-2); }
.dictee:active { transform: scale(.995); }
.dictee .dropzone__mic {
  width: 88px; height: 88px;
  background: var(--dv-accent);
  border-color: var(--dv-accent);
  color: var(--dv-paper);
}
.dictee .dropzone__mic svg { width: 32px; height: 32px; }
.dictee:hover .dropzone__mic { transform: scale(1.04); }

.dropzone__mic {
  position: relative;
  width: 68px; height: 68px;
  margin: 0 auto var(--dv-s-7);
  border: 1.5px solid var(--dv-accent);
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  color: var(--dv-accent);
  transition: background var(--dv-duree) var(--dv-sortie),
              border-color var(--dv-duree) var(--dv-sortie),
              transform var(--dv-duree) var(--dv-ressort);
}
.dropzone__mic svg { width: 26px; height: 26px; transition: opacity var(--dv-duree) var(--dv-sortie); }

/* Le halo de la dictée. Il bat sur l'horloge tant que le niveau du micro n'est
   pas mesurable, et sur la voix dès qu'il l'est — voir `--dv-niveau`. */
.dictee.enregistre .dropzone__mic {
  background: var(--dv-accent);
  color: var(--dv-paper);
}
.dictee.enregistre .dropzone__mic::after {
  content: "";
  position: absolute; inset: -8px;
  border-radius: 50%;
  border: 1.5px solid var(--dv-accent);
  opacity: .35;
  animation: halo 1.8s ease-out infinite;
}
@keyframes halo {
  0%   { transform: scale(.94); opacity: .38; }
  70%  { transform: scale(1.22); opacity: 0; }
  100% { transform: scale(1.22); opacity: 0; }
}

/* Les barres de niveau. Au repos elles n'existent pas ; pendant la dictée elles
   remplacent le micro et suivent la voix. Cinq barres, décalées, pour que le
   mouvement se lise comme un son et non comme un chargement. */
.onde {
  position: absolute; inset: 0;
  display: none; align-items: center; justify-content: center;
  gap: 3px;
}
.dictee.enregistre .onde { display: flex; }
.dictee.enregistre .dropzone__mic svg { opacity: 0; }
.onde i {
  display: block;
  width: 3px;
  border-radius: var(--dv-radius-pill);
  background: currentColor;
  /* `--dv-niveau` vaut 0 à 1, posé image par image depuis l'analyseur. Sans
     analyseur il reste nul et les barres respirent sur `dvpulse`. */
  height: calc(6px + 20px * var(--dv-niveau, 0));
  transition: height 90ms linear;
}
.dictee.enregistre:not(.a-niveau) .onde i { animation: dvpulse 1.1s ease-in-out infinite; }
.onde i:nth-child(1) { animation-delay: 0s;    height: calc(5px + 12px * var(--dv-niveau, 0)); }
.onde i:nth-child(2) { animation-delay: .12s;  height: calc(6px + 19px * var(--dv-niveau, 0)); }
.onde i:nth-child(3) { animation-delay: .24s;  height: calc(7px + 24px * var(--dv-niveau, 0)); }
.onde i:nth-child(4) { animation-delay: .36s;  height: calc(6px + 19px * var(--dv-niveau, 0)); }
.onde i:nth-child(5) { animation-delay: .48s;  height: calc(5px + 12px * var(--dv-niveau, 0)); }
@keyframes dvpulse {
  0%, 100% { height: 7px; }
  50%      { height: 22px; }
}

.dictee.enregistre { border-style: solid; border-color: var(--dv-accent); background: #fff; }

/* Le chronomètre pendant, la durée après : dans les deux cas un nombre, donc du
   mono, et à la taille d'un titre — c'est ce qu'on regarde. */
.dictee.enregistre .dropzone__title,
.dictee.relu .dropzone__title {
  font-family: var(--dv-mono);
  font-size: var(--dv-t-h2);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

/* Le point qui bat, dans la ligne d'aide. */
.dictee__point {
  display: inline-block;
  width: 7px; height: 7px;
  margin-right: var(--dv-s-3);
  border-radius: 50%;
  background: var(--dv-accent);
  animation: battement 1.4s ease-in-out infinite;
}
@keyframes battement { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }

/* En pause, plus rien ne bat : le halo, les barres et le point disent tous les
   trois « ça enregistre », et ce serait faux. La carte garde son état — le
   chronomètre figé reste à l'écran — et c'est le bouton dessous qui reprend. */
.dictee.en-pause .dropzone__mic::after { animation: none; opacity: 0; }
.dictee.enregistre.en-pause .onde i { animation: none; }
.dictee.en-pause .dictee__point { animation: none; opacity: .25; }

/* Côte à côte à toutes les largeurs, contrairement à `.relu__actions` : pendant la
   dictée on vise d'un pouce sans regarder, et les deux boutons ne doivent pas
   changer de place d'un téléphone à l'autre. */
.dictee-actions { display: flex; flex-wrap: wrap; gap: var(--dv-s-4); margin-top: var(--dv-s-5); }
.dictee-actions .btn { flex: 1; padding-left: var(--dv-s-4); padding-right: var(--dv-s-4); }
/* L'annulation prend la ligne suivante, sur toute la largeur des deux autres. */
.dictee-actions .btn--danger { flex-basis: 100%; }

/* ---- réécoute avant envoi ------------------------------------------------ */
/* La carte cesse d'être un bouton : ce sont le lecteur et les deux actions qui
   décident. Elle garde la durée, et perd son affordance. */

.dictee.relu {
  cursor: default;
  border-style: solid;
  border-color: var(--dv-rule);
  background: var(--dv-bg-alt);
  padding: var(--dv-s-9) var(--dv-s-7);
  box-shadow: none;
}
.dictee.relu:hover { border-color: var(--dv-rule); }
.dictee.relu .dropzone__mic {
  width: 52px; height: 52px; margin-bottom: var(--dv-s-5);
  /* La carte n'est plus un bouton : le micro redevient un simple trait. */
  background: none; color: var(--dv-accent);
}
.dictee.relu:hover .dropzone__mic { transform: none; }

.relu { margin-top: var(--dv-s-5); }
.relu__actions { display: flex; gap: var(--dv-s-4); margin-top: var(--dv-s-5); }
.relu__actions .btn { flex: 1; }
.relu audio { display: none; }

/* Le lecteur. Maison, parce qu'un webm sorti de MediaRecorder n'a pas de durée
   dans son en-tête et que le lecteur natif en invente une — « 32:04 » pour treize
   secondes de dictée. Ici la durée vient du chronomètre, qui ne ment pas. */
.lecteur {
  display: flex; align-items: center; gap: var(--dv-s-5);
  padding: var(--dv-s-3) var(--dv-s-5) var(--dv-s-3) var(--dv-s-3);
  background: var(--dv-paper);
  border: 1px solid var(--dv-rule);
  border-radius: var(--dv-radius-pill);
  box-shadow: var(--dv-ombre-1);
}
.lecteur__jouer {
  width: 44px; height: 44px; flex: none;
  display: flex; align-items: center; justify-content: center;
  border: none; border-radius: 50%;
  background: var(--dv-ink);
  cursor: pointer;
  transition: background var(--dv-duree-vite) var(--dv-sortie),
              transform var(--dv-duree-vite) var(--dv-ressort);
}
.lecteur__jouer:hover { background: var(--dv-accent); }
.lecteur__jouer:active { transform: scale(.94); }

/* Triangle de lecture en bordures : aucune image, aucun élément de plus. */
.lecteur__jouer i {
  width: 0; height: 0;
  margin-left: 3px;
  border-left: 11px solid var(--dv-paper);
  border-top: 7px solid transparent;
  border-bottom: 7px solid transparent;
}
/* Deux barres pour la pause, dessinées d'un dégradé — toujours pas d'élément. */
.lecteur.joue .lecteur__jouer i {
  width: 11px; height: 13px;
  margin-left: 0;
  border: none;
  background: linear-gradient(to right,
    var(--dv-paper) 0 4px, transparent 4px 7px, var(--dv-paper) 7px 11px);
}

.lecteur__piste {
  flex: 1;
  height: 6px;
  border-radius: var(--dv-radius-pill);
  background: var(--dv-rule);
  cursor: pointer;
  overflow: hidden;
}
.lecteur__avance {
  height: 100%; width: 0;
  border-radius: var(--dv-radius-pill);
  background: var(--dv-accent);
  transition: width 100ms linear;
}
.lecteur__temps { font-size: var(--dv-t-xs); color: var(--dv-muted); flex: none; }

/* Pendant la réécoute, les deux chemins de repli disparaissent : proposer trois
   façons d'envoyer un vocal qu'on vient d'enregistrer n'aide personne. */
.accueil.a-relu .replis { display: none; }

/* ---- les deux replis ----------------------------------------------------- */

.replis { margin-top: var(--dv-s-8); }

.paste-toggle {
  display: flex; align-items: center; gap: var(--dv-s-3);
  width: 100%;
  min-height: var(--dv-cible);
  padding: 0;
  border: none; background: none; cursor: pointer;
  font-family: var(--dv-sans);
  font-size: var(--dv-t-sm);
  color: var(--dv-muted);
  text-align: left;
  transition: color var(--dv-duree-vite) var(--dv-sortie);
}
.paste-toggle:hover { color: var(--dv-accent); }
.paste-toggle__signe {
  color: var(--dv-accent);
  transition: transform var(--dv-duree) var(--dv-ressort);
}
.paste-toggle[aria-expanded="true"] .paste-toggle__signe { transform: rotate(45deg); }

.paste { display: none; padding-bottom: var(--dv-s-6); }
.paste.is-open { display: block; animation: entree-repli var(--dv-duree) var(--dv-sortie) both; }
.paste .btn { margin-top: var(--dv-s-4); width: 100%; }
.que-dire {
  margin: 0;
  padding-left: 1.1em;
  font-size: var(--dv-t-sm);
  line-height: var(--dv-lh-body);
  color: var(--dv-ink);
}
.que-dire__exemple {
  margin: var(--dv-s-3) 0 0;
  font-family: var(--dv-serif);
  font-style: italic;
  font-size: var(--dv-t-sm);
  line-height: var(--dv-lh-body);
  color: var(--dv-muted);
}
@keyframes entree-repli {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}


/* ======================================================================== */
/* 2. Traitement                                                            */
/* ======================================================================== */

.traitement { align-items: flex-start; }
.traitement__inner { width: 100%; max-width: 720px; margin: 0 auto; }

.traitement__head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--dv-s-6);
  margin-bottom: var(--dv-s-7);
}
.traitement__head h2 {
  margin-top: var(--dv-s-2);
  font-size: var(--dv-t-h2);
  line-height: var(--dv-lh-snug);
}
.chrono {
  font-size: var(--dv-t-sm);
  color: var(--dv-faint);
  padding: var(--dv-s-2) var(--dv-s-4);
  background: var(--dv-bg-alt);
  border: 1px solid var(--dv-rule);
  border-radius: var(--dv-radius-pill);
  white-space: nowrap;
}

/* ---- la jauge ------------------------------------------------------------ */
/* Trois segments, un par étape du pipeline. Celui qui tourne se remplit d'une
   navette continue — il ne prétend pas connaître une progression qu'on n'a pas ;
   ceux qui sont faits sont pleins. La différence entre « ça avance » et « ça a
   fini » se voit d'un coup d'œil, à un mètre. */

.jauge { display: flex; gap: var(--dv-s-2); margin-bottom: var(--dv-s-9); }
.jauge__seg {
  flex: 1; height: 4px;
  border-radius: var(--dv-radius-pill);
  background: var(--dv-rule);
  overflow: hidden;
}
.jauge__seg i { display: block; height: 100%; width: 0; background: var(--dv-accent); }
.jauge__seg.est-faite i {
  width: 100%;
  transition: width var(--dv-duree-lente) var(--dv-sortie);
}
.jauge__seg.est-encours i {
  width: 40%;
  animation: navette 1.5s var(--dv-sortie) infinite;
}
@keyframes navette {
  0%   { transform: translateX(-105%); }
  100% { transform: translateX(255%); }
}

/* ---- les étapes ---------------------------------------------------------- */

.steps { display: flex; flex-direction: column; margin-bottom: var(--dv-s-9); }
.step {
  display: grid;
  grid-template-columns: 26px 1fr auto;
  align-items: start;
  gap: var(--dv-s-4);
  padding: var(--dv-s-5) 0;
  border-bottom: 1px solid var(--dv-rule-faint);
  transition: opacity var(--dv-duree) var(--dv-sortie);
}
.step:last-child { border-bottom: none; }
.step--attente { opacity: .48; }

.step__mark {
  width: 22px; height: 22px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  border: 1.5px solid var(--dv-rule-strong);
  color: var(--dv-accent);
  font-family: var(--dv-mono);
  font-size: var(--dv-t-label);
}
.step__mark svg { width: 13px; height: 13px; }
.step--faite .step__mark { border-color: var(--dv-accent); background: var(--dv-accent); color: var(--dv-paper); }
.step--encours .step__mark { border-color: var(--dv-accent); animation: battement 1.4s ease-in-out infinite; }

.step__title { font-size: var(--dv-t-body); font-weight: 500; line-height: var(--dv-lh-snug); }
.step__detail { font-size: var(--dv-t-sm); color: var(--dv-muted); margin-top: var(--dv-s-1); }
.step__status {
  font-family: var(--dv-mono);
  font-size: var(--dv-t-label);
  letter-spacing: var(--dv-track-label);
  text-transform: uppercase;
  color: var(--dv-faint);
  padding-top: 3px;
  white-space: nowrap;
}
.step--encours .step__status { color: var(--dv-accent); }

/* ---- la transcription qui se dévoile ------------------------------------- */
/* C'est le vrai contenu de cet écran : le moment où l'artisan voit que la machine
   l'a compris. Elle est donc composée pour être lue, pas pour meubler. */

.transcript-card {
  background: var(--dv-paper);
  border: 1px solid var(--dv-rule);
  border-radius: var(--dv-radius);
  padding: var(--dv-s-8) var(--dv-s-9);
  box-shadow: var(--dv-ombre-1);
  animation: entree-repli var(--dv-duree-lente) var(--dv-sortie) both;
}
.transcript-card .label { margin-bottom: var(--dv-s-5); }
.transcript-body {
  font-family: var(--dv-serif);
  font-size: var(--dv-t-lead);
  line-height: var(--dv-lh-loose);
  color: var(--dv-ink-soft);
  text-wrap: pretty;
}


/* ======================================================================== */
/* 3. Relecture                                                             */
/* ======================================================================== */

.relecture { align-items: flex-start; }
.relecture__inner { width: 100%; max-width: var(--dv-mesure-xl); margin: 0 auto; }

.relecture__head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--dv-s-8);
  margin-bottom: var(--dv-s-8);
}
.relecture__ident { min-width: 0; }
.relecture__head h2 {
  margin-top: var(--dv-s-2);
  font-size: var(--dv-t-h2);
  line-height: var(--dv-lh-snug);
  letter-spacing: var(--dv-track-tight);
  text-wrap: balance;
}
.relecture__client { font-size: var(--dv-t-sm); color: var(--dv-muted); margin-top: var(--dv-s-2); }
.synthese { margin-top: var(--dv-s-6); }
.synthese__montant { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--dv-s-3); }
.synthese__montant .num {
  font-size: var(--dv-t-h2);
  font-weight: 500;
  letter-spacing: -.01em;
  color: var(--dv-ink);
}
.synthese__libelle { font-size: var(--dv-t-sm); font-weight: 500; color: var(--dv-muted); }
.a-verifier {
  margin-top: var(--dv-s-6);
  padding: var(--dv-s-5) var(--dv-s-6);
  border-left: 3px solid var(--dv-accent);
  border-radius: var(--dv-radius);
  background: var(--dv-accent-tint);
}
.a-verifier__titre {
  font-family: var(--dv-mono);
  font-size: var(--dv-t-xs);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--dv-accent);
}
.a-verifier__points {
  margin: var(--dv-s-3) 0 var(--dv-s-4);
  padding-left: 1.1em;
  font-size: var(--dv-t-sm);
  line-height: var(--dv-lh-snug);
  color: var(--dv-ink);
}
.a-verifier__points li + li { margin-top: var(--dv-s-2); }
.synthese__detail { margin-top: var(--dv-s-1); font-size: var(--dv-t-sm); color: var(--dv-accent); }
.relecture__actions { display: flex; gap: var(--dv-s-4); flex-shrink: 0; }
/* L'échec du PDF, dit sous les boutons et pas sur un écran à part. */
.relecture__erreur {
  margin: calc(var(--dv-s-4) * -1) 0 var(--dv-s-7);
  font-size: var(--dv-t-sm);
  color: var(--dv-danger);
}

/* ---- le commutateur des deux vues ---------------------------------------- */
/* Visible seulement là où les deux vues ne tiennent pas côte à côte. */

.bascule {
  display: none;
  gap: var(--dv-s-1);
  padding: var(--dv-s-1);
  margin-bottom: var(--dv-s-7);
  background: var(--dv-bg-alt);
  border: 1px solid var(--dv-rule);
  border-radius: var(--dv-radius-pill);
}
.bascule__onglet {
  flex: 1;
  /* 44px et pas un de moins : c'est le minimum tactile d'Apple, et le
     rembourrage de 4px du conteneur ramène l'ensemble à la cible de 48. */
  min-height: 44px;
  border: none; background: none;
  border-radius: var(--dv-radius-pill);
  font-family: var(--dv-sans);
  font-size: var(--dv-t-sm);
  font-weight: 500;
  color: var(--dv-muted);
  cursor: pointer;
  transition: background var(--dv-duree) var(--dv-sortie),
              color var(--dv-duree) var(--dv-sortie),
              box-shadow var(--dv-duree) var(--dv-sortie);
}
.bascule__onglet.is-active {
  background: var(--dv-paper);
  color: var(--dv-ink);
  box-shadow: var(--dv-ombre-1);
}

.relecture__corps {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 400px;
  gap: var(--dv-s-11);
  align-items: start;
}
.relecture__detail { min-width: 0; }

/* ---- transcription repliée ------------------------------------------------ */

.transcript {
  margin-bottom: var(--dv-s-9);
  padding-bottom: var(--dv-s-6);
  border-bottom: 1px solid var(--dv-rule);
}
.transcript summary {
  display: flex; align-items: center; gap: var(--dv-s-3);
  min-height: 40px;
  cursor: pointer;
  list-style: none;
}
.transcript summary::-webkit-details-marker { display: none; }
.transcript summary::after {
  content: "";
  width: 7px; height: 7px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg) translateY(-2px);
  transition: transform var(--dv-duree) var(--dv-sortie);
}
.transcript[open] summary::after { transform: rotate(225deg) translateY(-2px); }
.transcript > div {
  margin-top: var(--dv-s-5);
  font-family: var(--dv-serif);
  font-size: var(--dv-t-body);
  line-height: var(--dv-lh-loose);
  color: var(--dv-ink-soft);
  text-wrap: pretty;
}

/* ---- le tableau des prestations ------------------------------------------ */

.table { margin-top: var(--dv-s-4); }
.table__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 56px 68px 104px 112px;
  gap: var(--dv-s-4);
  align-items: baseline;
}
.table__head {
  padding-bottom: var(--dv-s-3);
  border-bottom: 1.5px solid var(--dv-ink);
  font-family: var(--dv-mono);
  font-size: var(--dv-t-label);
  font-weight: 500;
  letter-spacing: var(--dv-track-label);
  text-transform: uppercase;
  color: var(--dv-faint);
}
.table__line {
  padding: var(--dv-s-5) 0;
  border-bottom: 1px solid var(--dv-rule-faint);
  font-size: var(--dv-t-sm);
}
.table__line:last-child { border-bottom: none; }
.table .r { text-align: right; }
.table .r.num { color: var(--dv-text); }
.table .unit { font-size: var(--dv-t-xs); color: var(--dv-muted); }
.table .total { color: var(--dv-ink); font-weight: 500; }
.table__desc { font-size: var(--dv-t-body); line-height: var(--dv-lh-snug); color: var(--dv-ink); }
.table__detail { margin-top: var(--dv-s-1); font-size: var(--dv-t-xs); line-height: var(--dv-lh-snug); color: var(--dv-muted); }

/* Le signal d'honnêteté, la marque du PDF. Discret volontairement : l'explication
   est dans l'encadré « À vérifier », en tête de relecture. */
.tag-estime {
  display: inline-flex; align-items: center; gap: var(--dv-s-2);
  margin-top: var(--dv-s-2);
  padding: 2px var(--dv-s-3);
  border-radius: var(--dv-radius);
  background: var(--dv-accent-tint);
  font-family: var(--dv-mono);
  font-size: 10.5px;
  letter-spacing: .04em;
  color: var(--dv-accent);
}

/* ---- observations et totaux ---------------------------------------------- */

.foot {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: var(--dv-s-10);
  margin-top: var(--dv-s-9);
  align-items: start;
}
.observations { display: flex; flex-direction: column; gap: var(--dv-s-4); margin-top: var(--dv-s-5); }
.observation {
  display: grid;
  grid-template-columns: 18px 1fr;
  gap: var(--dv-s-2);
  font-size: var(--dv-t-sm);
  line-height: var(--dv-lh-body);
  color: var(--dv-text);
}
.observation em { font-family: var(--dv-mono); color: var(--dv-accent); font-style: normal; }
.observation p { text-wrap: pretty; }

.totaux {
  background: var(--dv-paper);
  border: 1px solid var(--dv-rule);
  border-radius: var(--dv-radius);
  padding: var(--dv-s-7) var(--dv-s-7) var(--dv-s-5);
  box-shadow: var(--dv-ombre-1);
}
.totaux__row {
  display: flex; justify-content: space-between; gap: var(--dv-s-4);
  padding: var(--dv-s-2) 0;
  font-size: var(--dv-t-sm);
  color: var(--dv-text);
}
/* Le total TTC est le seul aplat d'encre de l'écran. C'est le chiffre que
   l'artisan cherche des yeux, et le seul que sa cliente retiendra. */
.totaux__ttc {
  display: flex; justify-content: space-between; align-items: baseline; gap: var(--dv-s-4);
  margin: var(--dv-s-4) calc(var(--dv-s-7) * -1) var(--dv-s-4);
  padding: var(--dv-s-4) var(--dv-s-7);
  background: var(--dv-ink);
  color: var(--dv-paper);
  font-family: var(--dv-serif);
  font-size: var(--dv-t-h3);
  font-weight: 600;
}
.totaux__ttc span:last-child { font-variant-numeric: tabular-nums; }
.totaux__acompte {
  display: flex; justify-content: space-between; gap: var(--dv-s-4);
  padding: var(--dv-s-3) 0;
  font-size: var(--dv-t-sm);
  font-weight: 500;
  color: var(--dv-accent);
}

/* ---- l'aperçu A4 ---------------------------------------------------------- */
/* La feuille telle qu'elle partira. Elle est collante sur un large écran : on
   descend dans le tableau, le document reste sous les yeux. */

.relecture__apercu { position: sticky; top: calc(var(--dv-entete-h) + var(--dv-s-6)); }

.apercu {
  border: 1px solid var(--dv-rule);
  border-radius: var(--dv-radius);
  overflow: hidden;
  background: var(--dv-desk);
  box-shadow: var(--dv-ombre-2);
}
.apercu__barre {
  display: flex; align-items: center; justify-content: space-between; gap: var(--dv-s-4);
  padding: var(--dv-s-4) var(--dv-s-5);
  background: var(--dv-paper);
  border-bottom: 1px solid var(--dv-rule);
}
.apercu__pages { font-size: var(--dv-t-label); color: var(--dv-faint); }

/* Le bureau sombre derrière la feuille. Hauteur bornée pour que le document se
   parcoure sans emporter la page entière. */
.apercu__bureau {
  position: relative;
  height: min(62vh, 620px);
  overflow: auto;
  overscroll-behavior: contain;   /* le défilement du document ne déborde pas sur la page */
  padding: var(--dv-s-5);
  background: var(--dv-desk);
}
/* Le plan occupe la place que la feuille prend une fois réduite ; le cadre garde
   sa largeur vraie et se met à l'échelle. Les deux tailles sont posées en JS —
   voir `ajusterApercu()`. */
.apercu__plan { position: relative; margin: 0 auto; }
.apercu__cadre {
  position: absolute; top: 0; left: 0;
  width: 794px;                    /* 210 mm à 96 ppp — la largeur réelle du A4 */
  /* Le `max-width: 100%` du socle vaut pour toute image ou tout cadre de la page,
     et il a raison partout ailleurs. Ici il rognerait la feuille à la largeur du
     bureau : le document se recomposerait, ses lignes se replieraient autrement,
     et l'aperçu cesserait de montrer le devis qui sort en PDF. C'est la mise à
     l'échelle qui réduit, jamais la largeur. */
  max-width: none;
  border: none;
  background: var(--dv-white);
  transform-origin: top left;
}

/* L'attente. Deux feuilles fantômes plutôt qu'un tourniquet : on annonce la
   forme de ce qui arrive, pas seulement le fait qu'on attend. */
.apercu__attente { display: flex; flex-direction: column; gap: var(--dv-s-5); }
.apercu__feuille {
  display: block;
  width: 100%;
  aspect-ratio: 210 / 297;
  border-radius: 2px;
  background: linear-gradient(100deg,
    rgba(255, 253, 250, .10) 30%,
    rgba(255, 253, 250, .20) 48%,
    rgba(255, 253, 250, .10) 66%);
  background-size: 300% 100%;
  animation: miroitement 1.6s ease-in-out infinite;
}
.apercu__feuille:nth-child(2) { animation-delay: .2s; opacity: .55; }
@keyframes miroitement {
  0%   { background-position: 130% 0; }
  100% { background-position: -30% 0; }
}


/* ======================================================================== */
/* 4. Devis vide                                                            */
/* ======================================================================== */

.vide { align-items: center; }
.vide__inner { width: 100%; max-width: var(--dv-mesure); }
.vide h2 {
  margin-top: var(--dv-s-4);
  font-size: var(--dv-t-h2);
  line-height: var(--dv-lh-snug);
  letter-spacing: var(--dv-track-tight);
  text-wrap: balance;
}
.vide__lede {
  font-size: var(--dv-t-lead);
  line-height: var(--dv-lh-body);
  color: var(--dv-muted);
  margin: var(--dv-s-5) 0 var(--dv-s-9);
  text-wrap: pretty;
}
.vide__quote {
  background: var(--dv-bg-alt);
  border-left: 2px solid var(--dv-accent);
  border-radius: 0 var(--dv-radius) var(--dv-radius) 0;
  padding: var(--dv-s-7) var(--dv-s-8);
  margin-bottom: var(--dv-s-9);
}
.vide__quote p {
  margin-top: var(--dv-s-4);
  font-family: var(--dv-serif);
  font-size: var(--dv-t-lead);
  line-height: var(--dv-lh-loose);
  color: var(--dv-text);
  text-wrap: pretty;
}
.vide .observations { margin-bottom: var(--dv-s-10); }
.vide .observation { font-size: var(--dv-t-body); }
.vide__conseils { margin-bottom: var(--dv-s-10); }
.vide__conseils .label { margin-bottom: var(--dv-s-3); }
.vide__actions { display: flex; gap: var(--dv-s-4); flex-wrap: wrap; }


/* ======================================================================== */
/* 5. Erreur                                                                */
/* ======================================================================== */
/* Même idiome que le devis vide : le produit dit ce qui s'est passé, en clair,
   et propose la reprise la moins coûteuse. Le rouge est réservé à ça. */

.erreur { align-items: center; }
.erreur__inner { width: 100%; max-width: var(--dv-mesure-etroite); }
.erreur h2 {
  margin-top: var(--dv-s-4);
  font-size: var(--dv-t-h2);
  line-height: var(--dv-lh-snug);
  text-wrap: balance;
}
.erreur__message {
  margin: var(--dv-s-5) 0 var(--dv-s-9);
  padding: var(--dv-s-5) var(--dv-s-6);
  background: var(--dv-danger-tint);
  border: 1px solid var(--dv-danger-edge);
  border-radius: var(--dv-radius);
  font-size: var(--dv-t-body);
  line-height: var(--dv-lh-body);
  color: var(--dv-danger);
  text-wrap: pretty;
}
.erreur__actions { display: flex; gap: var(--dv-s-4); flex-wrap: wrap; }


/* ======================================================================== */
/* 6. Template                                                              */
/* ======================================================================== */
/* Hors périmètre d'origine (CLAUDE.md § Périmètre), assumé. */

.template { align-items: flex-start; }
.template__inner { width: 100%; max-width: var(--dv-mesure); margin: 0 auto; }

.template__head {
  display: flex; flex-direction: column; align-items: flex-start; gap: var(--dv-s-7);
  margin-bottom: var(--dv-s-6);
}
.template__head h2 {
  margin-top: var(--dv-s-2);
  font-size: var(--dv-t-h2);
  line-height: var(--dv-lh-snug);
}
.template__lede {
  font-size: var(--dv-t-body);
  line-height: var(--dv-lh-body);
  color: var(--dv-text);
  margin-bottom: var(--dv-s-9);
  max-width: 58ch;
  text-wrap: pretty;
}

.template__etat {
  background: var(--dv-paper);
  border: 1px solid var(--dv-rule);
  border-radius: var(--dv-radius);
  padding: var(--dv-s-7) var(--dv-s-8);
  margin-bottom: var(--dv-s-7);
  box-shadow: var(--dv-ombre-1);
}
.template__nom {
  font-family: var(--dv-serif);
  font-size: var(--dv-t-h3);
  font-weight: 600;
  margin-top: var(--dv-s-3);
}
.template__meta { font-size: var(--dv-t-xs); color: var(--dv-muted); margin-top: var(--dv-s-2); }

/* Le contrôle des mentions obligatoires. C'est le prix de cette fonctionnalité,
   et il se paie à l'écran plutôt qu'en silence. Rouge et non brique : ce n'est
   pas un signal parmi d'autres, c'est un document qui n'est plus conforme. */
.template__alerte {
  display: flex; gap: var(--dv-s-5);
  background: var(--dv-danger-tint);
  border: 1px solid var(--dv-danger-edge);
  border-radius: var(--dv-radius);
  padding: var(--dv-s-7);
  margin-bottom: var(--dv-s-8);
  font-size: var(--dv-t-sm);
  line-height: var(--dv-lh-body);
  color: var(--dv-danger);
}
.template__alerte-icone { width: 20px; height: 20px; flex: none; margin-top: 2px; }
.template__alerte strong { font-weight: 600; }
.template__alerte p { margin: var(--dv-s-2) 0 var(--dv-s-4); }
.template__alerte ul { margin: 0; padding-left: var(--dv-s-6); }
.template__alerte li { margin-bottom: var(--dv-s-1); }

.template .dropzone { margin-top: var(--dv-s-5); }
.template__actions { display: flex; gap: var(--dv-s-4); margin-top: var(--dv-s-6); }

.template__message {
  margin-top: var(--dv-s-6);
  padding: var(--dv-s-4) var(--dv-s-5);
  border-radius: var(--dv-radius);
  font-size: var(--dv-t-sm);
  line-height: var(--dv-lh-body);
  color: var(--dv-muted);
  background: var(--dv-bg-alt);
  border: 1px solid var(--dv-rule);
}
.template__message--alerte {
  color: var(--dv-danger);
  background: var(--dv-danger-tint);
  border-color: var(--dv-danger-edge);
}
.template__message--ok {
  color: var(--dv-accent);
  background: var(--dv-accent-tint);
  border-color: var(--dv-accent-edge);
}

.template__pied {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--dv-s-5);
  margin-top: var(--dv-s-10);
  padding-top: var(--dv-s-7);
  border-top: 1px solid var(--dv-rule);
  font-size: var(--dv-t-sm);
}
.template__pied a {
  display: inline-flex; align-items: center; gap: var(--dv-s-3);
  min-height: var(--dv-cible);
}


/* ======================================================================== */
/* Adaptation                                                               */
/*                                                                          */
/* Le barème typographique étant fluide, il ne reste ici que ce qui change   */
/* réellement de forme : le nombre de colonnes, et le tableau qui s'empile.  */
/*                                                                          */
/*   1180px  l'aperçu A4 passe sous le commutateur, les deux vues alternent  */
/*    820px  observations et totaux repassent en une colonne                 */
/*    640px  le tableau s'empile, les actions prennent toute la largeur      */
/* ======================================================================== */

/* ---- 1180px : les deux vues de la relecture alternent -------------------- */
@media (max-width: 1180px) {
  .bascule { display: flex; }
  .relecture__corps { grid-template-columns: minmax(0, 1fr); gap: 0; }
  .relecture__inner { max-width: var(--dv-mesure-large); }

  /* Une seule vue à la fois. `hidden` sur les sections gérées par le
     commutateur : le lecteur d'écran ne doit pas lire la vue cachée. */
  .relecture__corps > [hidden] { display: none; }
  .relecture__apercu { position: static; }
  .apercu__bureau { height: min(70vh, 760px); }
}

/* Le même seuil, menu latéral ouvert. Il pousse la page de 272px (`--dv-menu-l`) :
   entre 1181 et 1452px de fenêtre, il ne reste donc plus la place des deux
   colonnes, alors que la requête ci-dessus, qui mesure la fenêtre, croit que si.
   `estEtroit()`, dans app.js, applique la même règle au `hidden` des deux vues. */
@media (min-width: 1181px) and (max-width: 1452px) {
  body.menu-ouvert .bascule { display: flex; }
  body.menu-ouvert .relecture__corps { grid-template-columns: minmax(0, 1fr); gap: 0; }
  body.menu-ouvert .relecture__inner { max-width: var(--dv-mesure-large); }
  body.menu-ouvert .relecture__corps > [hidden] { display: none; }
  body.menu-ouvert .relecture__apercu { position: static; }
  body.menu-ouvert .apercu__bureau { height: min(70vh, 760px); }
}

/* ---- 820px : observations et totaux en une colonne ---------------------- */
@media (max-width: 820px) {
  .foot { grid-template-columns: minmax(0, 1fr); gap: var(--dv-s-8); }
  .totaux { order: -1; }        /* le chiffre d'abord, les remarques ensuite */

  .relecture__head { flex-direction: column; gap: var(--dv-s-6); }
  .relecture__actions { width: 100%; }
  .relecture__actions .btn { flex: 1; }
}

/* ---- 640px : le tableau s'empile ---------------------------------------- */
/* La grille réserve 340px aux colonnes de chiffres ; sous cette largeur, la
   désignation n'a plus de place pour être lue. Chaque ligne devient donc un
   bloc, et les quatre chiffres passent dessous avec leur libellé — porté par
   `data-l`, posé en JS (voir `rangDeLigne`). */

@media (max-width: 640px) {
  .table__head { display: none; }
  .table {
    border-top: 1.5px solid var(--dv-ink);
    margin-top: var(--dv-s-5);
  }
  .table__line {
    display: block;
    padding: var(--dv-s-6) 0;
  }
  .table__line > div:first-child { margin-bottom: var(--dv-s-4); }
  .table__line .r {
    display: inline-flex;
    align-items: baseline;
    gap: var(--dv-s-2);
    margin-right: var(--dv-s-6);
    text-align: left;
    font-size: var(--dv-t-sm);
  }
  .table__line .r::before {
    content: attr(data-l);
    font-family: var(--dv-mono);
    font-size: var(--dv-t-label);
    letter-spacing: var(--dv-track-label);
    text-transform: uppercase;
    color: var(--dv-faint);
  }
  .table__line .total { margin-right: 0; }

  .relecture__actions,
  .vide__actions, .erreur__actions, .template__actions { flex-direction: column; }
  .relecture__actions .btn,
  .vide__actions .btn, .erreur__actions .btn, .template__actions .btn { width: 100%; }

  .relu__actions { flex-direction: column; }

  .entete__fil { display: none; }

  .traitement__head { flex-direction: column-reverse; align-items: flex-start; gap: var(--dv-s-4); }
  .step { grid-template-columns: 26px 1fr; }
  .step__status { display: none; }   /* redit ce que la pastille et la jauge disent déjà */

  .transcript-card { padding: var(--dv-s-7) var(--dv-s-6); }
  .totaux { padding: var(--dv-s-6) var(--dv-s-6) var(--dv-s-4); }
  .totaux__ttc { margin-left: calc(var(--dv-s-6) * -1); margin-right: calc(var(--dv-s-6) * -1); padding: var(--dv-s-4) var(--dv-s-6); }
}

/* ========================================================================== */
/* Identité de l'entreprise                                                   */
/*                                                                            */
/* La ligne d'accueil et l'écran qui va avec. Le SIRET n'est pas              */
/* demandé mais retrouvé : la recherche prend toute la largeur, le            */
/* formulaire se replie dessous.                                              */
/* ========================================================================== */

.identite {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  width: 100%; margin: 0 0 22px; padding: 13px 16px;
  background: var(--dv-paper); border: 1px solid var(--dv-rule);
  border-radius: var(--dv-radius); cursor: pointer; text-align: left;
  font: inherit; color: inherit; transition: border-color .18s;
}

/* Tant que l'identité manque, la ligne porte l'accent : le devis sortirait sans
   SIRET ni assurance, et ça ne doit pas se découvrir sur le PDF. */
.identite.est-vide { background: var(--dv-accent-tint); border-color: var(--dv-accent-edge); }

.identite__texte { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.identite__label { margin: 0; }
.identite__nom {
  font-size: 15px; color: var(--dv-ink); font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.identite.est-vide .identite__nom { color: var(--dv-accent-dark); font-weight: 400; }
/* Ce qui manque, en toutes lettres : « à renseigner » ne dit pas quoi. */
.identite__manque { font-size: 13px; line-height: 1.45; color: var(--dv-accent-dark); }
.identite__action {
  flex: none; font-family: var(--dv-mono); font-size: 12px; letter-spacing: .04em;
  text-transform: uppercase; color: var(--dv-accent);
}

.entreprise { min-height: 100svh; padding: 92px 24px 80px; display: flex; justify-content: center; }
.entreprise__inner { width: 100%; max-width: 720px; }
.entreprise__head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 20px; margin-bottom: 14px;
}
.entreprise__head h2 {
  margin: 4px 0 0; font-family: var(--dv-serif); font-size: 27px; font-weight: 600;
  color: var(--dv-ink);
}
.entreprise__lede { margin: 0 0 30px; font-size: 15px; color: var(--dv-muted); line-height: 1.6; }

/* La recherche : seule chose visible au premier coup d'œil. */
.chercher { margin-bottom: 8px; }
.chercher .label { display: block; margin-bottom: 9px; }
.chercher input {
  width: 100%; padding: 15px 16px; font: inherit; font-size: 17px;
  background: var(--dv-paper); color: var(--dv-ink);
  border: 1px solid var(--dv-border-input); border-radius: var(--dv-radius);
}
.chercher input:focus { outline: none; border-color: var(--dv-accent); }
.chercher__aide { margin-top: 9px; font-size: 13px; color: var(--dv-faint); min-height: 18px; }

.resultats {
  margin-top: 10px; background: var(--dv-paper);
  border: 1px solid var(--dv-rule); border-radius: var(--dv-radius); overflow: hidden;
}
.resultat {
  display: block; width: 100%; padding: 13px 16px; text-align: left;
  font: inherit; color: inherit; background: none; cursor: pointer;
  border: none; border-bottom: 1px solid var(--dv-rule-faint);
}
.resultat:last-child { border-bottom: none; }
.resultat__nom { font-size: 15px; color: var(--dv-ink); font-weight: 500; }
.resultat__meta { margin-top: 3px; font-size: 13px; color: var(--dv-faint); }
.resultat__meta .num { font-size: 12.5px; }

/* Formulaire replié : il existe, il ne s'impose pas. */
#entreprise-formulaire { margin-top: 4px; }
.groupe { margin-top: 26px; }
.groupe .section-head { margin-bottom: 14px; }
.groupe__note {
  margin: -4px 0 16px; font-size: 13px; color: var(--dv-faint); line-height: 1.55;
}
.grille { display: grid; grid-template-columns: 1fr 1fr; gap: 16px 18px; }
.champ--large { grid-column: 1 / -1; }
.champ label {
  display: block; margin-bottom: 6px; font-size: 13px; color: var(--dv-muted);
}
.champ__note { color: var(--dv-faint); font-size: 12px; }
/* 16px pour tous les champs, chiffres compris : en dessous, Safari iOS agrandit la
   page à chaque mise au point et la laisse agrandie. Ces règles venaient après celle
   de la section des formulaires et l'écrasaient à 15px, et à 14px en chasse fixe. */
.champ input {
  width: 100%; padding: 11px 13px; font: inherit; font-size: var(--dv-t-body);
  background: var(--dv-paper); color: var(--dv-ink);
  border: 1px solid var(--dv-border-input); border-radius: var(--dv-radius);
}
.champ input:focus { outline: none; border-color: var(--dv-accent); }
.champ input.num { font-family: var(--dv-mono); font-size: var(--dv-t-body); }
.champ__aide { margin-top: 6px; font-size: 12px; color: var(--dv-faint); min-height: 16px; }
.champ__aide.est-faux { color: var(--dv-accent-dark); }

.entreprise__actions { display: flex; gap: 12px; margin-top: 34px; }

@media (max-width: 560px) {
  .entreprise { padding: 56px 18px 60px; }
  .entreprise__head h2 { font-size: 23px; }
  .grille { grid-template-columns: 1fr; }
  .entreprise__actions { flex-direction: column; }
  .entreprise__actions .btn { width: 100%; }
}

/* ========================================================================== */
/* Porte d'entrée                                                             */
/*                                                                            */
/* Le code d'accès, demandé avant tout le reste quand le serveur en           */
/* réclame un. Distinct de l'écran de connexion : celui-ci dit qui            */
/* vous êtes, celui-là que vous avez le droit d'être ici.                     */
/* ========================================================================== */

.porte { min-height: 100svh; padding: 24px; display: flex; align-items: center; justify-content: center; }
.porte__inner { width: 100%; max-width: 420px; }
.porte__lede { margin: 18px 0 26px; font-size: 15px; color: var(--dv-muted); line-height: 1.6; }
.porte .label { display: block; margin-bottom: 9px; }
.porte input {
  width: 100%; padding: 15px 16px; font: inherit; font-size: 17px;
  font-family: var(--dv-mono); background: var(--dv-paper); color: var(--dv-ink);
  border: 1px solid var(--dv-border-input); border-radius: var(--dv-radius);
}
.porte input:focus { outline: none; border-color: var(--dv-accent); }
.porte__erreur { min-height: 20px; margin-top: 10px; font-size: 13px; color: var(--dv-accent-dark); }
.porte .btn { width: 100%; margin-top: 8px; }


/* ========================================================================== */
/* Édition du devis                                                           */
/*                                                                            */
/* Hors périmètre d'origine, ajoutée sur décision explicite. Elle reprend les */
/* champs et les listes de l'écran « Votre entreprise » : un artisan qui a    */
/* renseigné son identité reconnaît le geste.                                 */
/* ========================================================================== */

/* Ouvert par `scrollIntoView` : sans marge, son haut passerait sous l'en-tête fixe. */
.edition { scroll-margin-top: calc(var(--dv-entete-h) + var(--dv-s-6)); }
.edition .section-head { margin: 34px 0 14px; }
.edition > .section-head:first-child { margin-top: 0; }
.edition .paste-toggle { margin-top: 10px; }

/* Les suggestions d'adresse se posent sur ce qui suit au lieu de le pousser. */
.champ--suggestions { position: relative; }
.champ--suggestions .resultats {
  position: absolute; z-index: 5; top: 100%; left: 0; right: 0;
  margin-top: 4px; box-shadow: var(--dv-ombre-2);
}
.resultat[aria-selected="true"] { background: var(--dv-bg-alt); }

.edition__lignes { display: flex; flex-direction: column; gap: 12px; }

/* Une ligne : la désignation et les trois chiffres sur un rang, le détail et le
   statut du prix dessous. Pas de total — il n'existe qu'après le recalcul. */
.ligne-ed {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 84px 104px 112px 40px;
  gap: 10px 12px;
  align-items: end;
  padding: 14px;
  background: var(--dv-paper);
  border: 1px solid var(--dv-rule);
  border-radius: var(--dv-radius);
}
.ligne-ed.est-estimee { border-color: var(--dv-accent-edge); }
.ligne-ed .champ--detail { grid-column: 1 / 5; }
.ligne-ed .champ select {
  width: 100%; padding: 11px 13px; font: inherit; font-size: var(--dv-t-body);
  background: var(--dv-paper); color: var(--dv-ink);
  border: 1px solid var(--dv-border-input); border-radius: var(--dv-radius);
}
.ligne-ed input[aria-invalid="true"] { border-color: var(--dv-danger); background: var(--dv-danger-tint); }

.ligne-ed__retirer {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 44px; padding: 0;
  border: 1px solid transparent; border-radius: var(--dv-radius);
  background: none; color: var(--dv-faint); cursor: pointer;
}
.ligne-ed__retirer:hover { color: var(--dv-danger); border-color: var(--dv-danger-edge); }
.ligne-ed__retirer svg { width: 18px; height: 18px; }

/* Le statut du prix, sous la ligne. Coché, l'artisan prend le prix à son compte
   et la mention « estimé » disparaît du PDF. */
.ligne-ed__prix {
  grid-column: 1 / -1;
  display: inline-flex; align-items: center; gap: 8px;
  justify-self: start;
  padding: 4px 10px;
  border-radius: var(--dv-radius-pill);
  background: var(--dv-accent-tint);
  border: 1px solid var(--dv-accent-edge);
  font-family: var(--dv-mono); font-size: 11.5px; letter-spacing: .03em;
  color: var(--dv-accent);
  cursor: pointer;
}
.ligne-ed__prix input { width: 15px; height: 15px; margin: 0; accent-color: var(--dv-accent); }
.ligne-ed__prix:has(input:checked) { background: none; border-color: var(--dv-rule); color: var(--dv-muted); }

.edition__ajout { margin-top: 12px; }
.edition textarea {
  width: 100%; min-height: 110px; padding: 11px 13px; font: inherit; font-size: var(--dv-t-body);
  line-height: 1.55; resize: vertical;
  background: var(--dv-paper); color: var(--dv-ink);
  border: 1px solid var(--dv-border-input); border-radius: var(--dv-radius);
}
.edition textarea:focus { outline: none; border-color: var(--dv-accent); }

.edition__bandeau {
  margin: 0 0 var(--dv-s-7);
  padding: var(--dv-s-3) var(--dv-s-5);
  border-radius: var(--dv-radius);
  background: var(--dv-accent-tint);
  color: var(--dv-accent);
  font-size: var(--dv-t-sm);
}

/* La barre de validation colle au bas de l'écran tant que le formulaire défile.
   Fond opaque et filet au-dessus : les champs passent dessous sans s'y mêler.
   Valider à droite, sous le pouce ; Annuler en simple texte, à gauche. */
.edition__barre {
  position: sticky; bottom: 0; z-index: 5;
  margin-top: 22px;
  padding: var(--dv-s-4) 0 max(12px, env(safe-area-inset-bottom));
  background: var(--dv-bg);
  border-top: 1px solid var(--dv-rule);
}
.edition__erreur { margin-bottom: var(--dv-s-3); font-size: 14px; color: var(--dv-danger); }
.edition__erreur:empty { display: none; }
.edition__actions { display: flex; flex-direction: row-reverse; align-items: center; gap: 12px; }
.edition__actions #ed-valider { flex: 1; }
.btn--texte { background: none; color: var(--dv-ink); border-color: transparent; }
.btn--texte:hover { background: none; color: var(--dv-accent); text-decoration: underline; }

/* Pendant la saisie, le PDF prêt est celui d'avant : on ne le propose pas. « Nouveau
   devis » non plus : à portée de pouce sous le formulaire, il effaçait la correction
   en cours. On sort de l'édition par « Annuler ». */
.relecture.en-edition #btn-pdf,
.relecture.en-edition #btn-modifier,
.relecture.en-edition [data-recommencer] { display: none; }
/* Le total et l'encadré sont ceux d'avant la correction : aucun total n'est
   affiché pendant la saisie, ils reviennent recalculés à la validation. */
.relecture.en-edition .synthese,
.relecture.en-edition .a-verifier { display: none; }

@media (max-width: 640px) {
  /* La désignation prend tout le rang à côté de la croix : c'est le texte le plus
     long, et tronqué il ne se relit plus. Le prix récupère la colonne libre dessous. */
  .ligne-ed { grid-template-columns: 1fr 1fr 1fr 40px; }
  .ligne-ed .champ--designation { grid-column: 1 / 4; }
  .ligne-ed .champ--prix { grid-column: 3 / -1; }
  .ligne-ed .champ--detail { grid-column: 1 / -1; }
  .ligne-ed__retirer { grid-column: 4; grid-row: 1; justify-self: end; }
}


/* ======================================================================== */
/* Mouvement réduit                                                         */
/*                                                                          */
/* Le dévoilement de la transcription n'est pas supprimé, il est instantané  */
/* (voir `MOUVEMENT_REDUIT` dans app.js) : c'est du contenu, pas de la       */
/* décoration. Tout le reste s'arrête.                                       */
/* ======================================================================== */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}
