/* Police Inter HÉBERGÉE EN LOCAL (fichier variable : couvre les graisses 100→900 en un
   seul .woff2). Aucune dépendance réseau ; repli system-ui si le fichier manque. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/inter-variable.woff2") format("woff2");
}
/* Sous-ensemble GREC (α, β, γ…) — sinon ces caractères basculent sur une police système
   (rendu plus petit / plus fin que le latin). unicode-range = bascule auto par caractère. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/inter-greek.woff2") format("woff2");
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}

:root {
  /* Surfaces & neutres */
  --bg: #eaeef4;
  --card: #ffffff;
  --surface-2: #f4f6fa;
  --border: #dde3ec;
  --line: #e7ebf1;
  --readonly-bg: #eff2f7;

  /* Encre / texte (encre légèrement marine pour coller à la marque) */
  --text: #14283a;
  --muted: #79818f;

  /* ── IDENTITÉ UNIVEIRA — couleurs OFFICIELLES du Cahier des normes graphiques (éd. 2018,
     v3.83) : marine Pantone 2955C #00395f + bleu clair Pantone 7689C #5795c5 (les valeurs
     approchées historiques #01395f / #83a4cb sont alignées sur la charte). ── */
  --brand-navy: #00395f;     /* marine du logotype (Pantone 2955C) */
  --brand-steel: #5795c5;    /* bleu clair du logotype (Pantone 7689C) */
  --brand-gradient: linear-gradient(135deg, #5795c5 0%, #00395f 100%);  /* rappel du logomark */

  /* Accent = bleu de marque + variantes */
  --accent: #15567f;         /* bleu marine moyen (interactions, liens, étapes) */
  --accent-strong: #00395f;  /* marine de marque (états forts — Pantone 2955C, v3.83) */
  --accent-soft: #e9f0f6;    /* fond bleu très clair */
  --accent-line: #b9d0e4;    /* bord bleu acier clair */

  /* Sémantiques */
  --ok: #15803d;
  --warn: #c2410c;
  --err: #dc2626;

  /* Rayons & ombres — (v3.72) échelle RESSERRÉE (revue design « pro ») : cartes 10, contrôles 6.
     La pilule (999px) est RÉSERVÉE aux statuts/badges — quand tout est pilule, rien n'est un statut. */
  --r-sm: 6px;
  --r: 10px;
  --shadow-sm: 0 1px 2px rgba(20,30,50,.06);
  --shadow: 0 1px 2px rgba(20,30,50,.04), 0 8px 24px rgba(20,30,50,.06);

  /* ── ÉCHELLE TYPOGRAPHIQUE (v3.92, revue design « pro » — point 3) : 12 PALIERS, consolidés
     depuis ~25 valeurs ad hoc (fusions ≤ 0,05 rem, sans changement de densité perceptible).
     ⚠️ Toute NOUVELLE font-size doit prendre un de ces paliers — ne pas en inventer :
       0.64  badges / chips (« estimé », dyn., n° d'étape)
       0.72  aides, notes, mentions, effets de paramètre
       0.78  petits libellés, dividers de section, méta
       0.82  libellés secondaires, petits boutons, corps dense admin
       0.85  boutons standard, libellés de champ, tableaux
       0.9   corps courant (notes de devis, cartes, saisies)
       0.95  corps appuyé (champs, selects, valeurs de réglage)
       1     base (aide contact, texte long)
       1.05  titres de carte (bandeau), champ A mis en avant
       1.14  grands titres internes (nom de tuile inclus → 1.25)
       1.25  titres de scène / d'écran (tuiles, pickers en mobile)
       1.5   titre de page (bandeau) et marque (connexion) */
}

* { box-sizing: border-box; }

body {
  font-family: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  /* (v3.76) FOND D'IMAGE fourni par l'utilisateur (Fond.pdf → PNGs/fond.png, Ghostscript) :
     motif géométrique clair, posé en COVER FIXE sous un VOILE blanc translucide — le motif
     reste perceptible sans nuire à la lisibilité des cartes blanches. Repli : la couleur
     de fond bleu-gris si l'image manque. (À l'impression : fond blanc, cf. @media print.) */
  background-color: #e9eff3;
  background-image:
    linear-gradient(180deg, rgba(247, 250, 252, 0.75) 0%, rgba(233, 241, 245, 0.82) 100%),
    url("../PNGs/fond.png");
  background-size: auto, cover;
  background-position: center;
  background-attachment: fixed;
  color: var(--text);
  margin: 0;
  padding: 38px 20px 64px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

.container {
  max-width: 1160px;
  margin: 0 auto;
}

/* ── Bandeau d'en-tête (identité + titre + version) ── */
.app-header {
  display: flex;
  align-items: center;
  gap: 18px;
  /* (v3.89, corrigé v3.90) Couleur PANTONE 7689 C officielle (#5795c5, --brand-steel), pas de
     teinte inventée. (v3.90 — retour utilisateur : « toujours trop clair ») Le dégradé 0%→100%
     de la v3.89 étalait le mélange clair/foncé sur TOUTE la largeur → la majeure partie du
     bandeau restait plus claire que le Pantone. Le point de couleur PURE est avancé à 22% :
     l'éclaircissement ne reste visible que dans le coin gauche, tout le reste du bandeau
     affiche la couleur officielle telle quelle. */
  background: linear-gradient(90deg, #83b1d4 0%, #5795c5 22%);
  color: var(--brand-navy);
  border-bottom: 1px solid var(--border);
  padding: 15px 30px;
  /* Full-bleed : on compense le padding du body (38px haut, 20px gauche/droite) → bannière bord à bord. */
  margin: -38px -20px 18px;
  box-shadow: var(--shadow);
}
/* Bannière PLEINE LARGEUR (v1.88) : le header est hors du .container (1160px) → il s'étend bord à bord. */
/* (v3.85) LOGO BICOLORE D'ORIGINE, fluide (filtres « blanc » v3.84 et plaque v3.83 retirés) :
   sur la bannière claire, le logotype officiel ressort naturellement. */
.app-brand {
  display: flex;
  align-items: center;
  gap: 11px;
  padding-right: 18px;
  border-right: 1px solid rgba(0, 57, 95, 0.18);
  white-space: nowrap;
}
.app-logo { height: 42px; width: auto; display: block; }
/* Wordmark = vraie typo officielle (découpée du logo) — marine d'origine sur fond clair. */
.app-wordmark-img { height: 21px; width: auto; display: block; }
.app-title {
  font-size: 1.5rem;          /* titre de page DOMINANT (échelle typo claire, v2.77) */
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.2;
  margin: 0;
}
/* (v3.23) .app-subtitle, .app-version et .client-test-btn : éléments retirés du bandeau
   (revue dirigeants) — styles supprimés. */

/* ── (v3.80/v3.82) SOUS-BARRE D'EN-TÊTE (vue client) : barre blanche pleine largeur COLLÉE au
   bandeau marine (marges négatives : -18px = le margin-bottom du header, -20px = le padding
   latéral du body). Depuis la v3.82 elle ne porte QUE le fil d'avancement, CENTRÉ (retour
   utilisateur — les repères de confiance sont montés dans le bandeau, sous le titre). ── */
.header-sub {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 6px 28px;
  margin: -18px -20px 24px;
  padding: 7px 28px;
  background: var(--card);
  border-bottom: 1px solid var(--border);
  box-shadow: var(--shadow-sm);
}
@media print { .header-sub, .trust-bar { display: none !important; } }

/* Repères de confiance (v2.80) : DANS le bandeau depuis la v3.82, sous le titre —
   (v3.85) petits liens bleu accent sur la bannière CLAIRE. */
.app-title-group { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.trust-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px 9px;
  font-size: 0.72rem;
  color: var(--muted);
}
.trust-item { font-weight: 600; }
.trust-flag { margin-right: 5px; }
.trust-sep  { color: var(--muted); }
/* (v3.88) Marine PLEIN (au lieu de --accent, plus clair) — la bannière assombrie réduisait
   trop le contraste de l'ancien bleu accent (~2,4:1). Hover : encre la plus sombre du site. */
.trust-link { color: var(--brand-navy); text-decoration: none; }
.trust-link:hover, .trust-link:focus-visible { text-decoration: underline; color: var(--text); }

/* ── FIL D'AVANCEMENT client (v3.64, #clientSteps) : 4 étapes numérotées — étape courante en
   accent plein, étapes passées cochées (✓), à venir grisées. Purement indicatif (non cliquable).
   (v3.80) Groupe de la sous-barre, plus une bande à part. ── */
.client-steps {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 4px 20px; margin: 0; padding: 0;
  list-style: none; counter-reset: step;
  font-size: 0.78rem; font-weight: 600; color: var(--muted);
}
.client-steps li { display: inline-flex; align-items: center; gap: 7px; }
.client-steps li::before {
  content: attr(data-step);
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; border-radius: 50%;
  border: 1px solid var(--accent-line); background: var(--surface-2);
  font-size: 0.72rem; font-weight: 700; color: var(--muted);
}
.client-steps li.is-current { color: var(--accent-strong); }
.client-steps li.is-current::before { background: var(--accent-strong); border-color: var(--accent-strong); color: #fff; }
.client-steps li.is-done { color: var(--accent); }
.client-steps li.is-done::before { content: "✓"; background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent); }
/* (Phase 2c) L'étape « Type d'outil » franchie est cliquable → retour aux tuiles (app.js). */
.client-steps li[data-step="1"].is-done { cursor: pointer; }
.client-steps li[data-step="1"].is-done:hover { text-decoration: underline; }
@media print { .client-steps { display: none; } }

/* ── CONNEXION PROTOTYPE (v3.69) : écran plein + badge de profil + déconnexion ──
   ⚠️ Cosmétique, pas une sécurité (comme client-only/admin-only) — vraie identification
   serveur à la mise en ligne. */
.login-screen { position: fixed; inset: 0; z-index: 3000; display: flex; align-items: center;
  justify-content: center; padding: 20px; background: var(--brand-gradient); }
.login-screen[hidden] { display: none; }
.login-card { background: #fff; border-radius: 16px; padding: 36px 40px; max-width: 430px;
  width: 100%; box-shadow: 0 24px 70px rgba(1,32,56,.45); text-align: center; }
.login-brand { font-size: 1.5rem; font-weight: 800; letter-spacing: 0.14em; color: var(--brand-navy); }
.login-sub { margin: 4px 0 0; color: var(--muted); font-size: 0.9rem; }
.login-ask { margin: 26px 0 4px; font-weight: 600; color: var(--text); font-size: 0.95rem; }
.login-btn { display: block; width: 100%; margin-top: 12px; padding: 13px 16px;
  font: inherit; font-size: 0.95rem; font-weight: 700; border-radius: var(--r-sm);
  cursor: pointer; transition: filter .15s, background .15s; }
.login-btn--client { border: none; background: var(--brand-gradient); color: #fff; }
.login-btn--client:hover { filter: brightness(1.12); }
/* .login-btn--admin : couleurs héritées du SYSTÈME DE BOUTONS niveau 2 (fin de feuille, v3.91). */
.login-note { margin: 20px 0 0; font-size: 0.78rem; color: var(--muted); line-height: 1.5; }
/* Connecté : la vue découle du profil → commutateur Client/Admin masqué ; badge à sa place. */
body.auth-client .view-switch, body.auth-admin .view-switch { display: none; }
.role-badge { margin-left: auto; font-size: 0.82rem; font-weight: 700; color: var(--brand-navy); white-space: nowrap; }   /* (v3.85, opacité retirée v3.88 : bannière assombrie) */
.role-badge[hidden] { display: none; }
@media print { .login-screen, .role-badge, #btnLogout { display: none !important; } }

/* ── Commutateur de vue CLIENT / ADMIN (bandeau) ── */
/* (v3.85) Bannière CLAIRE : le commutateur passe en marine-sur-clair (actif = pilule marine). */
.view-switch {
  margin-left: auto;
  display: flex;
  gap: 2px;
  background: rgba(0, 57, 95, 0.08);
  border-radius: 999px;
  padding: 3px;
}
.view-btn {
  border: none;
  background: transparent;
  color: rgba(0, 57, 95, 0.88);   /* (v3.88) 0.7 → 0.88 : bannière assombrie, il fallait plus de contraste */
  font-size: 0.82rem;
  font-family: inherit;
  padding: 4px 16px;
  border-radius: 999px;
  cursor: pointer;
}
.view-btn.active {
  background: var(--brand-navy);
  color: #fff;
  font-weight: 600;
}

/* (v5.06) La case « Voir les formules » du bandeau a été retirée : en vue admin, chaque champ à
   formule porte sa flèche ▸ qui déplie/édite SA formule (styles .formula-toggle plus bas). */

/* (v3.23) Le FIL D'ÉTAPES (stepper horizontal v1.0 + rail vertical v1.87) est SUPPRIMÉ (revue
   dirigeants). On conserve la largeur de contenu élargie des écrans larges (le layout client à
   3 colonnes — schéma | cotes | devis compact — en profite). */
@media (min-width: 1200px) {
  body.view-client .container { max-width: 1380px; }
}

/* ── VUE CLIENT : ne garder que l'essentiel (sélection, schémas, dimensions,
   couple + alerte, prix). Le « moteur » (formules, encombrement, usinage,
   variables, détail du devis) est réservé à la vue ADMIN.
   ⚠️ Masquage PUREMENT VISUEL (aperçu de la future séparation client/serveur) :
   le vrai cloisonnement se fera côté serveur — ce n'est PAS de la sécurité. ── */
body.view-client .admin-only   { display: none !important; }
body.view-client .formula-edit { display: none !important; }
/* Notes techniques masquées, SAUF la note de couple max et l'état du devis
   (les deux informations que le client doit voir). */
body.view-client .auto-note:not(#coupleMaxNote):not(#devisNote) { display: none !important; }

/* ── VUE ADMIN : mode COMPACT (v0.80, refondu v0.81) ──
   L'admin voit les MÊMES champs que le client (source de vérité unique : la valeur
   retenue est rappelée en NOIR à côté du libellé — .label-val), mais en densité
   maximale : FORMULES masquées par défaut (case « Voir les formules » du bandeau
   pour tout afficher, flèche ▸ d'un champ pour les siennes), cartes Dimensions +
   Options FUSIONNÉES en une liste dense sans titres (déplacement par setView). ── */

/* (v5.06) Formules masquées par défaut ; affichées CHAMP PAR CHAMP via la flèche ▸ du champ
   (bascule .formula-open sur le champ → SA formule seulement). Plus de case globale « Voir les
   formules ». L'attribut [hidden] — formule inapplicable au contexte (ex. B hors fourche) —
   garde toujours la priorité. */
body.view-admin .formula-edit { display: none; }
body.view-admin .field.formula-open .formula-edit:not([hidden]) { display: flex; }
/* (v5.28) Panneau tuyauter ALIGNÉ sur le régime « par champ » (demande utilisateur — les
   formules et provenances s'affichaient d'office, sans flèche). La flèche ▸ de la RANGÉE
   (.tuy-cote-label, câblée par buildTuyPanel) pose .tuy-open sur les deux cellules ; la
   FORMULE ne se montre en plus que si c'est ELLE qui a fixé la valeur (.tuy-formula-used —
   MIN(palier voisin, formule) gagné par la formule : « Plafonnée / Estimée par formule »). */
body.view-admin .tuy-admin.tuy-open.tuy-formula-used .formula-edit:not([hidden]) { display: flex; }
body.view-admin .tuy-cote-label .formula-toggle { display: inline-block; }

/* ── MODULE COUPLE (RdM) lisible en vue admin (v1.43) : les 3 modes de ruine sont TOUJOURS
   affichés en lignes claires [mode · formule éditable · résultat]. Le mode qui GOUVERNE le
   couple max (le min des 3 = maillon faible) est DÉMARQUÉ en bleu (informatif). Il ne passe
   en ROUGE (alarmant) que si le couple CIBLE saisi DÉPASSE cette capacité (.mode-danger).
   (Le client, lui, ne voit aucune formule.) ── */
/* (v5.10) Les 3 modes de ruine se DÉPLIENT via la flèche ▸ du champ Couple (comme tous les
   champs à formule — demande utilisateur : la flèche ne « faisait rien ») : masqués par défaut,
   affichés quand le champ porte .formula-open. */
body.view-admin .couple-field.formula-open .formula-edit:not([hidden]) {
  display: flex !important; align-items: center; gap: 8px; margin: 5px 0; padding: 5px 9px;
  border: 1px solid var(--border); border-radius: var(--r-sm); background: #fff;
}
body.view-admin .couple-field .formula-label { flex: 0 0 96px; margin: 0; font-size: 0.78rem; font-weight: 600; color: var(--text); white-space: nowrap; }
body.view-admin .couple-field .formula-input { flex: 1 1 auto; min-width: 0; }   /* (v5.10) taille = base commune */
body.view-admin .couple-field .formula-result { flex: 0 0 auto; font-size: 0.82rem; font-weight: 700; font-style: normal; color: var(--text); white-space: nowrap; }
/* Maillon faible DÉMARQUÉ (bleu, informatif — pas d'alarme). */
body.view-admin .couple-field .formula-edit.mode-governs { background: var(--accent-soft); border-color: var(--accent-line); box-shadow: 0 0 0 1px var(--accent-line) inset; }
body.view-admin .couple-field .formula-edit.mode-governs .formula-label,
body.view-admin .couple-field .formula-edit.mode-governs .formula-result { color: var(--accent-strong); }
/* ALARME (rouge) : posée par refreshCoupleDisplay UNIQUEMENT quand le couple cible saisi
   dépasse la capacité (le plus faible couple calculé). */
body.view-admin .couple-field .formula-edit.mode-governs.mode-danger { background: #fdecec; border-color: var(--err); box-shadow: 0 0 0 2px var(--err) inset; }
body.view-admin .couple-field .formula-edit.mode-governs.mode-danger .formula-label,
body.view-admin .couple-field .formula-edit.mode-governs.mode-danger .formula-result { color: var(--err); }
/* (v5.10) La flèche ▸ du champ Couple est RÉACTIVÉE : elle déplie/replie les 3 modes de ruine
   (câblage générique des flèches — .formula-open sur le champ — déjà en place). */

/* ── Bannière COUPLE (vue admin, v1.45) : le module RdM séparé des cotes, SOUS les cotes +
   options. Le couple cible CLIENT y est en LECTURE SEULE (pas de champ d'ajustement). ── */
body.view-admin #cardCouple .label-val { display: none; }   /* valeur déjà lisible dans le champ */
body.view-admin #cardCouple #couple { background: var(--surface-2); cursor: default; }
body.view-admin #cardCouple #couple:focus { outline: none; box-shadow: none; }

/* ── CATALOGUE RÉALISÉ (Phase 4, v1.46) : tableau éditable des pièces réalisées. ── */
#cardCatalogue .cat-sub { color: var(--muted); font-size: 0.85rem; margin: 0 0 14px; }
.cat-table-wrap { overflow-x: auto; }
.cat-table { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
.cat-table th { text-align: left; font-size: 0.72rem; font-weight: 700; letter-spacing: 0.03em; color: var(--muted); padding: 4px 8px; border-bottom: 1px solid var(--border); }  /* pas d'uppercase : garde α/β/γ minuscules (v2.87) */
.cat-table td { padding: 3px 6px; border-bottom: 1px solid var(--line); }
.cat-table tbody tr:hover { background: var(--surface-2); }
.cat-table .cat-ref { color: var(--muted); font-size: 0.78rem; white-space: nowrap; }
.cat-cell { width: 54px; padding: 5px 7px; font-size: 0.85rem; border: 1px solid var(--border); border-radius: 0; font-variant-numeric: tabular-nums; background: #fff; }
.cat-cell:focus { border-color: var(--accent); outline: none; box-shadow: 0 0 0 2px var(--accent-soft); }
.cat-del { border: none; background: none; color: var(--muted); font-size: 1.14rem; line-height: 1; cursor: pointer; padding: 2px 7px; border-radius: var(--r-sm); }
.cat-del:hover { color: var(--err); background: #fdecec; }

/* ── Catalogue admin — TABLEAU ÉDITABLE « grille » (comme prix acier, v2.90) : réfs en lignes,
   cotes + options en colonnes. En-tête + 1re colonne (réf) COLLANTS pour parcourir un large
   tableau ; cases VIDES grisées, valeurs renseignées en évidence (blanches + semi-grasses). ── */
.cat-table-wrap { max-height: 62vh; overflow: auto; border: 1px solid var(--line); border-radius: var(--r-sm); }
.cat-table--grid { border-collapse: separate; border-spacing: 0; min-width: 100%; }
.cat-table--grid thead th { position: sticky; top: 0; z-index: 3; background: var(--surface-2); border-bottom: 2px solid var(--border); padding: 8px 9px; text-align: center; white-space: nowrap; }
.cat-table--grid td { padding: 4px 6px; border-bottom: 1px solid var(--line); text-align: center; vertical-align: middle; }
/* Zébrure douce + surlignage de ligne au survol. */
.cat-table--grid tbody tr:nth-child(even) { background: var(--surface-2); }
.cat-table--grid tbody tr:hover { background: var(--accent-soft); }
/* Colonne RÉFÉRENCE : collante à gauche (fond opaque suivant la zébrure/survol). */
.cat-table--grid th.cat-ref-col, .cat-table--grid td.cat-ref-col { position: sticky; left: 0; z-index: 2; text-align: left; white-space: nowrap; }
.cat-table--grid thead th.cat-ref-col { z-index: 4; }
.cat-table--grid tbody td.cat-ref-col { background: #fff; font-weight: 600; color: var(--text); font-size: 0.82rem; box-shadow: 1px 0 0 var(--line); }
.cat-table--grid tbody tr:nth-child(even) td.cat-ref-col { background: var(--surface-2); }
.cat-table--grid tbody tr:hover td.cat-ref-col { background: var(--accent-soft); }
/* Cases : remplie = blanche + semi-grasse (ressort) ; VIDE = grisée + placeholder discret. */
.cat-table--grid .cat-cell { margin: 0 auto; text-align: center; }
.cat-table--grid .cat-cell:not(.cat-cell--empty) { background: #fff; font-weight: 600; color: var(--text); }
.cat-table--grid .cat-cell--empty { background: var(--surface-2); border-color: var(--line); color: var(--muted); }
.cat-table--grid .cat-cell--empty::placeholder { color: var(--border); }
.cat-table--grid .cat-cell--empty:focus { background: #fff; color: var(--text); border-color: var(--accent); }
.cat-table--grid .cat-cell--emp { width: 92px; text-align: left; }
/* Plan / validation / suppression : centrés, compacts. */
.cat-table--grid .cat-plan-cell { display: flex; align-items: center; justify-content: center; gap: 6px; white-space: nowrap; }
.cat-table--grid .cat-val-cell { text-align: center; }
.cat-empty { color: var(--muted); font-size: 0.85rem; margin: 0 0 10px; }
/* (v4.11 → v4.15) LIGNES STANDARD STAHLWILLE — RÉFÉRENCE intégrée au tableau, intercalée par A.
   (v4.15, demande utilisateur) HARMONISÉES avec les lignes UNIVEIRA : plus de fond teinté ni de
   réf accentuée — elles s'affichent à l'identique (mêmes cases, éditables si « Éditer les
   valeurs »), seule la réf « Stahlwille … » les distingue. La réf reste sur une ligne. */
.cat-table--grid tbody tr.cat-std-row td.cat-ref-col { white-space: nowrap; }
.cat-actions { margin-top: 14px; }

/* (v4.10) Barre au-dessus du tableau : sous-titre à gauche, case « Éditer les valeurs » à
   DROITE. Les cases sont NON éditables par défaut ; cliquer une case verrouillée remonte à
   cette case (défilement fluide) et l'ENCADRE (animation). */
.cat-editbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 6px; }
#cardCatalogue .cat-editbar .cat-sub { margin: 0; }
.cat-editlab { display: inline-flex; align-items: center; gap: 7px; font-size: 0.82rem; font-weight: 600; color: var(--text); cursor: pointer; white-space: nowrap; padding: 5px 11px; border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--card); transition: border-color .12s, box-shadow .12s, background .12s; }
.cat-editlab:hover { border-color: var(--accent); background: var(--accent-soft); }
.cat-editlab input { accent-color: var(--accent); cursor: pointer; margin: 0; }
.cat-editlab--flash { animation: catEditFlash 1.6s ease-out; }
@keyframes catEditFlash {
  0%, 45% { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); background: var(--accent-soft); }
  100%    { border-color: var(--border); box-shadow: 0 0 0 3px rgba(1,57,95,0); background: var(--card); }
}
/* Tableau VERROUILLÉ : les cases invitent au clic (curseur), la suppression est masquée
   (édition indisponible tant que « Éditer les valeurs » n'est pas coché).
   (v4.14) Les cases VERROUILLÉES s'affichent comme des VALEURS, pas des champs (retour
   utilisateur : distinguer visiblement l'éditable du non-éditable) — pas de bordure/fond de
   champ, pas de liseré au focus. L'aspect « champ » (bordure + fond blanc) est réservé au
   mode ÉDITION. */
.cat-table--locked .cat-cell[readonly] {
  cursor: pointer;               /* clic → rappel « Éditer les valeurs » */
  border-color: transparent; background: transparent; box-shadow: none;
}
.cat-table--locked .cat-cell[readonly]:focus { border-color: transparent; box-shadow: none; }
.cat-table--locked .cat-cell--empty[readonly] { background: transparent; }   /* vide : pas la trame grise du champ */
.cat-table--locked .cat-del { display: none; }
/* (v4.17) Bouton « Changer » / « + Plan » du plan client : visible SEULEMENT en mode ÉDITION
   (demande utilisateur) — masqué tant que « Éditer les valeurs » n'est pas coché. La miniature
   (.cat-thumb) reste, pour consulter/agrandir le plan. */
.cat-table--locked .cat-plan { display: none; }
/* (v4.60) RÉFÉRENCE ÉDITABLE (input) : rendue quand la ligne est éditable — « Éditer les valeurs »
   coché, OU pièce « en attente » (nouvellement ajoutée). Aspect champ, comme .cat-cell. */
.cat-ref-input { width: 120px; max-width: 120px; padding: 5px 7px; font-size: 0.82rem; font-weight: 600; color: var(--text); border: 1px solid var(--border); border-radius: 0; background: #fff; font-family: inherit; }
.cat-ref-input:focus { border-color: var(--accent); outline: none; box-shadow: 0 0 0 2px var(--accent-soft); }
.cat-lastcell { white-space: nowrap; }
/* (v4.60) Bouton « Valider » temporaire d'une pièce en attente : finalise la ligne (retire l'édition
   forcée → elle rejoint le tri par A et repasse en lecture seule sauf « Éditer les valeurs »). */
.cat-valider { border: 1px solid var(--accent); background: var(--accent); color: #fff; font: inherit; font-size: 0.76rem; font-weight: 600; cursor: pointer; padding: 4px 11px; border-radius: var(--r-sm); margin-right: 4px; }
.cat-valider:hover { background: var(--accent-strong); border-color: var(--accent-strong); }
/* Ligne « en attente » : léger repère ambré + contrôles rétablis même quand la table est verrouillée. */
.cat-table--grid tbody tr.cat-row-pending td, .cat-table--grid tbody tr.cat-row-pending td.cat-ref-col { background: #fffdf2; }
.cat-table--locked .cat-row-pending .cat-del, .cat-table--locked .cat-row-pending .cat-plan { display: inline-block; }
/* (v4.13) PRIX ACIER verrouillé (même système que le catalogue) : barre d'édition à droite
   (le sous-titre est un paragraphe à part), suppression masquée, cases en VALEURS (v4.14). */
.cat-editbar--end { justify-content: flex-end; }
.prix-locked .prix-del-table { display: none; }
.prix-locked .prix-cell[readonly], .prix-locked .prix-ref[readonly],
.prix-locked .prix-fourn[readonly], .prix-locked .prix-lb[readonly] {
  cursor: pointer;
  border-color: transparent; background: transparent; box-shadow: none;
}
.prix-locked .prix-cell[readonly]:focus, .prix-locked .prix-ref[readonly]:focus,
.prix-locked .prix-fourn[readonly]:focus, .prix-locked .prix-lb[readonly]:focus { border-color: transparent; box-shadow: none; }

/* (v4.09) INFOBULLE MAISON (.has-tip + data-tip) — apparition IMMÉDIATE (le `title` natif était
   lent ~1 s + style OS incohérent, retour utilisateur) et aux couleurs du site (marine). Ancrée
   SOUS l'élément et à DROITE (croît vers la gauche) pour ne pas être rognée par le conteneur
   scrollable du catalogue (#catalogueTable, overflow:auto). Réutilisable partout. */
.has-tip { cursor: help; }
.has-tip::after {
  content: attr(data-tip);
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 20;
  max-width: 240px; white-space: normal; text-align: left;
  background: var(--brand-navy); color: #fff;
  font-size: 0.72rem; font-weight: 500; line-height: 1.35; letter-spacing: 0; text-transform: none;
  padding: 7px 10px; border-radius: var(--r-sm); box-shadow: var(--shadow);
  opacity: 0; transform: translateY(-3px); pointer-events: none;
  transition: opacity 0.12s ease, transform 0.12s ease;
}
.has-tip:hover::after, .has-tip:focus-visible::after { opacity: 1; transform: translateY(0); }
/* La cellule d'en-tête « valid. » est déjà `position: sticky` (donc positionnée → ancre de
   l'infobulle) ; ailleurs, .has-tip fournit lui-même le contexte de positionnement. */
.has-tip:not(th):not(td) { position: relative; }
/* Sélecteur de PAGE du catalogue (type + attachement, v1.87). */
.cat-nav { display: flex; flex-wrap: wrap; gap: 18px; margin: 4px 0 12px; }
.cat-nav label { display: inline-flex; align-items: center; gap: 7px; font-size: 0.82rem; font-weight: 600; color: var(--text); }
.cat-nav select { font: inherit; font-size: 0.82rem; padding: 5px 9px; border: 1px solid var(--border); border-radius: var(--r-sm); background: #fff; color: var(--text); cursor: pointer; }
.cat-nav select:focus { border-color: var(--accent); outline: none; box-shadow: 0 0 0 2px var(--accent-soft); }
/* Vue catalogue (v1.72) : barre d'outils + vue COMPACTE (réf + A, cotes dépliables au chevron). */
.cat-toolbar { margin: 0 0 8px; }
.btn-mini { font: inherit; font-size: 0.78rem; padding: 4px 11px; border: 1px solid var(--border); border-radius: var(--r-sm); background: #fff; cursor: pointer; color: var(--text); }
.btn-mini:hover { background: var(--accent-soft); border-color: var(--accent-line); }
.cat-table--compact .cat-ref { font-size: 0.82rem; color: var(--text); font-weight: 600; }
.cat-table th.cat-exp, .cat-table td.cat-exp { width: 26px; padding-left: 4px; padding-right: 0; }
.cat-expand { background: none; border: none; cursor: pointer; font-size: 0.72rem; color: var(--accent); padding: 2px 4px; line-height: 1; }
.cat-expand:hover { color: var(--accent-strong); }
.cat-detail > td { padding: 2px 8px 10px 22px; background: var(--surface-2); border-bottom: 1px solid var(--line); }   /* 22px = largeur colonne ▶ : aligne les cotes sous la référence (v2.88) */

/* Champs (inputs/selects) des onglets Catalogue & Prix acier : coins DROITS, pour
   cohérence avec les cellules du tableau (lecture dense). Les BOUTONS gardent leurs
   coins arrondis — distinction claire champ/action (v2.88). */
#cardCatalogue input, #cardCatalogue select,
#cardPrixAcier input, #cardPrixAcier select { border-radius: 0; }
.cat-detail-grid { display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-end; }
.cat-detail-field { display: flex; flex-direction: column; gap: 3px; font-size: 0.72rem; }
/* Pas de text-transform:uppercase : les cotes sont déjà en majuscules (A/B/H/S…) et l'uppercase
   transformerait les options grecques α/β/γ en Α/Β/Γ (≡ A/B/Γ latins) → faux doublons (v2.87). */
.cat-detail-field > span { color: var(--muted); letter-spacing: 0.03em; font-size: 0.64rem; font-weight: 700; }
.cat-detail-actions { margin-left: auto; align-self: center; }
/* Vue compacte v1.93 : cotes/options VISIBLES en lecture seule par défaut ; la flèche passe la ligne en
   édition. Une cellule readonly s'affiche comme une VALEUR (sans bordure ni fond) ; en édition elle
   reprend l'aspect d'un champ (la règle de base input[type=...] redonne bordure/fond). */
.cat-cell[readonly] { border-color: transparent; background: transparent; box-shadow: none; cursor: default; padding-left: 2px; }
.cat-detail-emp .cat-cell--emp[readonly] { white-space: nowrap; }
.cat-detail .cat-detail-actions { display: none; }               /* suppression cachée hors édition */
.cat-detail.cat-editing .cat-detail-actions { display: block; }
.cat-expand.is-editing { color: var(--accent-strong); font-weight: 700; }
.cat-detail.cat-editing > td { background: var(--accent-soft); }  /* ligne en cours d'édition mise en avant */
/* Ligne NON éditée (réf repliée) : cotes + libellés plus petits → lecture rapide (v2.05). */
.cat-detail:not(.cat-editing) .cat-detail-grid { gap: 5px 12px; }
.cat-detail:not(.cat-editing) .cat-detail-field { font-size: 0.64rem; gap: 0; }
.cat-detail:not(.cat-editing) .cat-detail-field > span { font-size: 0.64rem; }
.cat-detail:not(.cat-editing) .cat-cell[readonly] { font-size: 0.72rem; }
.cat-detail:not(.cat-editing) .cat-cell:not(.cat-cell--emp)[readonly] { width: 40px; }
/* Options à zéro / empreinte vide : masquées en lecture seule, réaffichées à l'édition (v2.05). */
.cat-detail:not(.cat-editing) .cat-detail-field.cat-opt-zero { display: none; }

/* Recherche par référence (v2.05) : champ + table de résultats. */
.cat-search-lab input { font: inherit; font-size: 0.82rem; padding: 5px 9px; border: 1px solid var(--border); border-radius: var(--r-sm); background: #fff; color: var(--text); min-width: 150px; }
.cat-search-lab input:focus { border-color: var(--accent); outline: none; box-shadow: 0 0 0 2px var(--accent-soft); }
.cat-table--search .cat-search-meta { color: var(--muted); font-size: 0.78rem; white-space: nowrap; }
.cat-table--search .cat-search-cotes { color: var(--muted); font-size: 0.78rem; }
.cat-table--search td { padding: 6px 8px; vertical-align: middle; }
.cat-goto { white-space: nowrap; }
.cat-search-count { color: var(--muted); font-size: 0.78rem; margin: 8px 0 0; }
.cat-table--search .cat-ref mark { background: var(--accent-soft); color: var(--accent-strong); font-weight: 700; padding: 0 1px; border-radius: 2px; }
/* (v4.16) RÉSULTATS DE RECHERCHE au MÊME FORMAT que le tableau normal : un groupe par
   (type × attachement), chacun rendu par le tableau `detailed()` (mêmes cases, éditables si
   « Éditer les valeurs »). Titre de groupe accentué au-dessus de chaque tableau. */
.cat-search-group { margin-bottom: 20px; }
.cat-search-h { margin: 0 0 8px; font-size: 0.85rem; font-weight: 700; color: var(--accent-strong); }
/* Boutons d'action du CATALOGUE (v1.87) : style UNIFORME = celui de « Ajouter la pièce courante ». */
/* Barre de boutons du bas — catalogue ET prix acier partagent le MÊME style uniforme (v2.88). */
#catalogueActions, #prixAcierActions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
/* Couleurs/survol : SYSTÈME DE BOUTONS niveau 2 (fin de feuille, v3.91) — ici : tailles seules. */
#catalogueActions button,
#catalogueActions .prix-import-label,
#prixAcierActions button,
#prixAcierActions .prix-import-label {
  font: inherit; font-size: 0.85rem; padding: 8px 14px; margin: 0;
}
.cat-mem-note { color: var(--warn); font-size: 0.78rem; margin: 12px 0 0; }
/* (v5.11) Variante « enregistré » (mode SERVEUR : gamme + prix acier persistés en base v5.07) —
   la note passe d'avertissement à CONFIRMATION (posée par updatePersistNotes, app.js). */
.cat-mem-note--ok { color: #15803d; }

/* (v5.16) TOAST D'ENREGISTREMENT (mode serveur) : « ✓ Enregistré » discret en bas à droite après
   chaque sauvegarde gamme/prix ; l'ÉCHEC (session expirée, réseau) s'affiche en rouge et RESTE
   visible 10 s — avant, il était silencieux. Créé par showSaveToast (app.js). */
#saveToast {
  position: fixed; right: 18px; bottom: 18px; z-index: 1000;
  padding: 9px 16px; border-radius: 8px;
  background: #15803d; color: #fff; font-size: 0.85rem; font-weight: 600;
  box-shadow: 0 4px 14px rgba(16, 24, 40, 0.18);
  opacity: 0; transform: translateY(6px); pointer-events: none;
  transition: opacity 0.18s, transform 0.18s;
}
#saveToast.save-toast--on { opacity: 1; transform: none; }
#saveToast.save-toast--err { background: #b91c1c; max-width: 380px; line-height: 1.4; }

/* Grilles tarifaires acier (Phase 4b, v1.47) : largeur (lignes) × épaisseur (colonnes). */
.prix-table-block { margin-bottom: 26px; }
/* En-tête d'un tableau : prioritaire · nom · longueur de barre · supprimer. */
.prix-table-head { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin: 0 0 8px; }
.prix-prio { display: inline-flex; align-items: center; gap: 5px; font-size: 0.82rem; font-weight: 600; color: var(--accent-strong); white-space: nowrap; }
.prix-ref { flex: 1 1 240px; min-width: 180px; font: inherit; font-size: 0.85rem; font-weight: 600; color: var(--text); padding: 6px 9px; border: 1px solid var(--border); border-radius: var(--r-sm); }
.prix-ref:focus { border-color: var(--accent); outline: none; box-shadow: 0 0 0 2px var(--accent-soft); }
.prix-lb-wrap { font-size: 0.78rem; color: var(--muted); white-space: nowrap; }
.prix-lb { width: 64px; font: inherit; font-size: 0.82rem; padding: 5px 7px; border: 1px solid var(--border); border-radius: 0; text-align: right; }
.prix-del-table { border: 1px solid var(--border); background: #fff; color: var(--muted); font: inherit; font-size: 0.78rem; padding: 6px 10px; border-radius: var(--r-sm); cursor: pointer; }
.prix-del-table:hover { color: var(--err); border-color: var(--err); background: #fdecec; }
.prix-table { width: auto; }   /* largeur-contenu : les colonnes gardent leur taille, scroll horizontal */
.prix-table th { white-space: nowrap; }
.prix-table td { padding: 3px 4px; }
.prix-table .prix-corner { text-align: right; color: var(--muted); font-size: 0.64rem; position: sticky; left: 0; background: var(--card); z-index: 2; }
.prix-table .prix-rowhead { text-align: right; font-weight: 700; color: var(--text); font-size: 0.85rem; position: sticky; left: 0; background: var(--card); padding-right: 10px; }
.prix-table thead th { position: sticky; top: 0; background: var(--card); z-index: 1; font-size: 0.78rem; color: var(--text); padding: 6px 8px; }
.prix-cell { width: 72px; padding: 7px 9px; font-size: 0.9rem; border: 1px solid var(--border); border-radius: 0; font-variant-numeric: tabular-nums; background: #fff; }
.prix-cell:focus { border-color: var(--accent); outline: none; box-shadow: 0 0 0 2px var(--accent-soft); }
.prix-table .cat-table-wrap, #prixAcierTables .cat-table-wrap { max-height: 62vh; overflow: auto; border: 1px solid var(--line); border-radius: var(--r-sm); }
/* Bouton « Importer un Excel » (label enrobant un input file caché). */
/* Couleurs/survol : SYSTÈME DE BOUTONS niveau 2 (fin de feuille, v3.91) — ici : tailles seules. */
.prix-import-label { display: inline-block; font-size: 0.85rem; padding: 8px 14px; }
/* (v3.91) La règle « neutre » historique de #btnPrixExport était du code MORT (la règle scopée
   #prixAcierActions button la battait en spécificité) : supprimée — l'uniformité v2.88 est le
   comportement réel, désormais porté par le SYSTÈME DE BOUTONS (fin de feuille). */
#btnPrixAddTable { font: inherit; font-size: 0.85rem; padding: 8px 14px; }
/* Panneau de choix à l'import (mettre à jour un tableau / ajouter un nouveau). */
.prix-import-choice { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 12px 0 0; padding: 12px 14px; background: var(--accent-soft); border: 1px solid var(--accent-line); border-radius: var(--r-sm); font-size: 0.85rem; }
.prix-import-choice select { font: inherit; font-size: 0.85rem; padding: 6px 8px; border: 1px solid var(--border); border-radius: var(--r-sm); background: #fff; }
.prix-import-choice button { font: inherit; font-size: 0.82rem; font-weight: 600; padding: 6px 12px; border-radius: var(--r-sm); cursor: pointer; border: 1px solid var(--border); background: #fff; }
#prixImportConfirm { background: var(--accent); color: #fff; border-color: var(--accent); }
#prixImportConfirm:hover { background: var(--accent-strong); }
/* Panneau d'erreurs d'import (valeurs illisibles) — rouge, pleine largeur, liste lisible. */
.prix-import-errors { flex-direction: column; align-items: stretch; background: #fdecec; border-color: #e3b0b0; }
.prix-import-errlist { margin: 4px 0 2px; padding-left: 20px; max-height: 240px; overflow: auto; }
.prix-import-errlist li { margin: 2px 0; line-height: 1.4; }
.prix-import-errlist .muted { color: #7a6a6a; }
#prixImportErrClose { align-self: flex-start; }
/* Panneau « Sauvegardes » (5 derniers états téléchargeables) — v1.72. */
.cat-backups { flex-direction: column; align-items: stretch; }
.cat-backups .bk-list { display: flex; flex-wrap: wrap; gap: 8px; margin: 4px 0 2px; }
.bk-dl, .bk-close { font: inherit; font-size: 0.82rem; padding: 6px 11px; border: 1px solid var(--border); border-radius: var(--r-sm); background: #fff; cursor: pointer; }
.bk-dl { border-color: var(--accent-line); background: var(--accent-soft); color: var(--accent-strong); font-weight: 600; }
.bk-dl:hover { filter: brightness(0.97); }
.bk-close { align-self: flex-start; }
.bk-close:hover { background: var(--accent-soft); }
#btnCatBackups, #btnPrixBackups { font: inherit; font-size: 0.85rem; padding: 8px 12px; border: 1px solid var(--border); border-radius: var(--r-sm); background: #fff; cursor: pointer; }
#btnCatBackups:hover, #btnPrixBackups:hover { background: var(--accent-soft); border-color: var(--accent-line); }
/* La carte Prix acier prend TOUTE la largeur disponible (pas le plafond 720px des autres
   onglets) → un maximum de colonnes visibles avant défilement horizontal. */
body.view-admin #cardPrixAcier { max-width: none; }
/* Largeurs FORCÉES via #id (bat la règle globale `input[type=number]{width:100%}`, qui sinon
   écrasait les cellules à la largeur du tableau → valeurs illisibles) + min-width anti-écrasement. */
#cardPrixAcier .prix-table { width: auto; }
#cardPrixAcier .prix-cell { width: 76px; min-width: 76px; padding: 6px 8px; box-sizing: border-box; font-size: 0.9rem; }   /* étroit, calé sur « 9999,99 » (v1.92) */
#cardPrixAcier .prix-lb { width: 72px; box-sizing: border-box; padding: 5px 7px; text-align: right; }
#cardPrixAcier .prix-ref { width: auto; }
#cardPrixAcier .prix-fourn { width: 130px; box-sizing: border-box; padding: 6px 9px; font-size: 0.85rem; font-weight: 600; color: var(--text); }
.prix-fourn-wrap { display: inline-flex; align-items: center; gap: 6px; font-size: 0.78rem; color: var(--muted); white-space: nowrap; }

/* Effet/impact de chaque paramètre, affiché sous la valeur (carte Paramètres, v1.43). */
.param-eff { display: block; font-size: 0.72rem; color: var(--muted); margin-top: 2px; line-height: 1.3; }

/* ── (v3.72) Onglet VARIABLES en LIGNES TABULAIRES (revue design « pro ») : fini la grille de
   champs flottants — chaque paramètre = une LIGNE « libellé + effet (gauche) · champ étroit
   aligné à droite », séparée par un filet fin (style page de réglages). Les champs restent
   ÉDITABLES tels quels (mêmes inputs, mêmes ids, valeur auto grise / saisie noire intactes) ;
   minmax(0,1fr) = pas de débordement sur téléphone. ── */
/* (v3.73-74, retours utilisateur) DEUX COLONNES **PAR SECTION** : gauche = RdM, droite =
   Encombrement & usinage + Devis (le remplissage ligne à ligne v3.73 mélangeait les sections —
   structure .var-col dans index.html). Chaque paire libellé·champ reste compacte (écart 16 px) ;
   sous 760 px les colonnes s'empilent. */
body.view-admin #cardVariables .grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: 40px;
  align-items: start;   /* la colonne la plus courte ne s'étire pas */
}
body.view-admin #cardVariables .var-col > .field {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 130px;
  column-gap: 16px; row-gap: 1px;
  align-items: center;
  padding: 9px 2px;
  border-bottom: 1px solid var(--line);
}
body.view-admin #cardVariables .var-col > .field:last-child { border-bottom: none; }
body.view-admin #cardVariables .var-col > .field > label { grid-column: 1; grid-row: 1; margin: 0; font-weight: 500; }
body.view-admin #cardVariables .var-col > .field > .param-eff { grid-column: 1; grid-row: 2; margin: 0; }
body.view-admin #cardVariables .var-col > .field > input {
  grid-column: 2; grid-row: 1 / span 2;
  width: 130px; justify-self: end;
  text-align: right; font-variant-numeric: tabular-nums;
  /* (v3.73) Valeurs plus AFFIRMÉES (retour utilisateur : elles se perdaient) : graisse et
     taille montées — la COULEUR reste le code de provenance (gris #4b5563 = autocomplétée,
     noir = saisie manuelle), inchangée. */
  font-weight: 600; font-size: 0.95rem;
}
body.view-admin #cardVariables .section-divider { padding-top: 14px; margin-top: 6px; border-top: none; }
body.view-admin #cardVariables .section-divider--first { padding-top: 4px; margin-top: 0; }
@media (max-width: 760px) {
  body.view-admin #cardVariables .grid { grid-template-columns: minmax(0, 1fr); }
}

/* ── (v3.72) Finitions transverses (revue design « pro ») ──
   Chiffres TABULAIRES sur tout ce qui affiche valeurs et prix (colonnes qui s'alignent au
   pixel) + transitions hover/focus uniformes sur les contrôles qui n'en avaient pas. */
input[type="number"],
.cart-card-price, .cop-prix, .cart-order-meta, .cart-modal-total,
.order-card-meta, .pieces-total, .piece-card-prix,
.deg-pu, .deg-tot, .devis-cout, input.price-badge,
#cardDevisCompact .dc-price, .recap-v { font-variant-numeric: tabular-nums; }
.btn-validate, .admin-tab, .orders-tab, .type-tile, .btn-mini, .picker-back, .orders-sort-lab select {
  transition: background 0.12s, border-color 0.12s, color 0.12s, box-shadow 0.12s;
}

/* En-tête de CATÉGORIE dans le détail du devis (admin), v1.43 : pleine largeur (interrompt
   les colonnes), les postes de la catégorie s'écoulent dessous. */
.devis-cat { -webkit-column-span: all; column-span: all; font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--accent-strong); margin: 9px 0 4px; padding-bottom: 3px; border-bottom: 1px solid var(--accent-line); }
.devis-cat:first-child { margin-top: 0; }

/* ════════════════════════════════════════════════════════════════════════════
   ONGLETS ADMIN (v1.43, phase 3) — une section à la fois, mise en page 1 colonne
   ════════════════════════════════════════════════════════════════════════════ */
/* (Phase 2) Fil d'Ariane admin — reflète la route courante (#/…), au-dessus des onglets. */
.admin-crumb { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0 0 12px; font-size: 0.82rem; }
/* (v4.65) Le contenu du fil (retour + maillons) est « transparent » au flex → ses enfants sont
   des items directs du <nav> ; les actions de validation (dernier item) filent à DROITE, sur la
   même ligne que « ← Retour ». */
.admin-crumb .crumb-content { display: contents; }
.admin-crumb .detail-bar-actions { margin-left: auto; }
.admin-crumb .crumb-link { color: var(--brand-steel); text-decoration: none; font-weight: 600; }
.admin-crumb .crumb-link:hover { text-decoration: underline; }
.admin-crumb .crumb-cur { color: var(--brand-navy); font-weight: 700; }
.admin-crumb .crumb-sep { color: var(--muted); }
/* (v4.28) Bouton « ← Retour » intégré au fil d'Ariane (vers le parent immédiat). */
/* (v5.15) « ← Retour » UNIFIÉ avec le fil d'Ariane (demande utilisateur : trop de couleurs et
   de formats sur cette ligne) : plus de pilule blanche à bordure — même typographie que les
   maillons cliquables (acier, 600), soulignement au survol. Et l'emplacement est RÉSERVÉ
   (fantôme .crumb-back--ghost, invisible mais mesuré) quand il n'y a pas de parent → le fil
   ne se décale plus quand le bouton apparaît/disparaît. */
.admin-crumb .crumb-back { display: inline-flex; align-items: center; gap: 5px; margin-right: 14px;
  padding: 5px 0; border: none; background: none;
  color: var(--brand-steel); font-weight: 600; text-decoration: none; }
.admin-crumb .crumb-back:hover { text-decoration: underline; color: var(--brand-navy); }
.admin-crumb .crumb-back .cb-arrow { font-size: 1.05em; line-height: 1; }
.admin-crumb .crumb-back--ghost { visibility: hidden; pointer-events: none; }

/* (Phase 2b) LAYOUT ADMIN à SIDEBAR : navigation latérale (Travail / Référentiels) + contenu.
   Le header scrolle ; la sidebar est STICKY dans le flex. */
body.view-admin .admin-layout { display: flex; align-items: flex-start; gap: 22px; max-width: 1420px; margin: 0 auto; }
body.view-admin .admin-layout > .container { margin: 0; max-width: none; flex: 1 1 auto; min-width: 0; }
/* (v5.15) Largeur STRICTEMENT fixe (min/max + overflow) : le contenu (« Devis en cours »,
   libellés longs) ne peut plus élargir la sidebar → plus AUCUN décalage du contenu quand on
   ouvre un devis (demande utilisateur). Les libellés longs sont tronqués par leur ellipsis. */
.admin-sidebar { flex: 0 0 210px; width: 210px; min-width: 210px; max-width: 210px; overflow: hidden;
  position: sticky; top: 16px; display: flex; flex-direction: column; gap: 0;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--r); padding: 6px 10px 10px; box-shadow: var(--shadow-sm); }
.asb-group { display: flex; flex-direction: column; gap: 2px; padding: 14px 0 4px; }
/* (v4.70, demande utilisateur) L'attribut [hidden] doit BATTRE `display: flex` : le groupe
   « Devis en cours » (#asbDevisGroup) n'apparaît QUE dans un devis (renderSidebarDevis pose/retire
   `hidden` selon la route). Sinon la classe .asb-group le laissait visible (label vide). */
.asb-group[hidden] { display: none; }
/* (v4.69) SÉPARATION NETTE entre les groupes (Travail / Référentiels / Devis en cours). (v5.06)
   Renforcée : filet plus contrasté (--border) + espace vertical au-dessus du groupe, pour une
   organisation lisible des sections (demande utilisateur). */
.asb-group + .asb-group { border-top: 1px solid var(--border); margin-top: 6px; padding-top: 16px; }
/* (v5.06) Surtitre de section : discret mais lisible. */
.asb-label { margin: 0 0 7px; padding: 0 8px; font-size: 0.7rem; letter-spacing: 0.09em; text-transform: uppercase; font-weight: 700; color: var(--brand-steel); }
/* (v5.06, renforcé v5.08) Une SEULE police et un SEUL gabarit pour TOUS les éléments cliquables
   de la sidebar — items de menu, récap/pièces du devis en cours ET « Coordonnées du client »
   (.asb-coords) : 0.9rem / 600 / padding 8px 10px / gap 10px. La hiérarchie passe par la couleur
   et les pastilles, jamais par la taille (harmonisation, demande utilisateur). */
.asb-item, .asb-devis-recap, .asb-devis-piece, .asb-coords {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; border: none; background: transparent;
  color: var(--text); font-family: inherit; font-size: 0.9rem; font-weight: 600; padding: 8px 10px; border-radius: var(--r-sm);
  cursor: pointer; transition: background 0.15s, color 0.15s; }
.asb-item:hover, .asb-coords:hover { background: var(--accent-soft); color: var(--brand-navy); }
.asb-item.is-active { background: var(--brand-navy); color: #fff; }
.asb-ico { width: 17px; height: 17px; flex: 0 0 auto; }
/* (v5.16) Pastille « n à traiter » sur « Devis & commandes » : même langage que les pastilles du
   « Devis en cours » (0.7rem, pilule), poussée à DROITE de l'item ; ambre = travail en attente. */
.asb-count { flex: 0 0 auto; margin-left: auto; font-size: 0.7rem; font-weight: 700; padding: 1px 7px;
  border-radius: 999px; background: #fef3c7; color: #92400e; }
.asb-count[hidden] { display: none; }
.asb-item.is-active .asb-count { background: rgba(255, 255, 255, 0.22); color: #fff; }

/* (v4.66) SIDEBAR « Devis en cours » : récapitulatif + pièces cliquables (navigation + retour).
   (v5.08) Le gabarit commun (police, padding, gap) vient de la règle partagée ci-dessus — ne
   restent ici que les ACCENTS : le récap se distingue par la couleur + le compteur, la pièce par
   sa pastille de numéro. */
.asb-devis { display: flex; flex-direction: column; gap: 2px; }
.asb-devis-recap { color: var(--accent-strong); margin-bottom: 2px; }
.asb-devis-recap:hover, .asb-devis-piece:hover { background: var(--accent-soft); color: var(--brand-navy); }
.asb-devis-recap.is-active, .asb-devis-piece.is-active { background: var(--brand-navy); color: #fff; }
.adr-label { flex: 1 1 auto; min-width: 0; }
/* Compteur de pièces (pastille), à droite du récapitulatif. (v5.08 : 0.7rem, aligné sur .adp-num.) */
.asb-devis-count { flex: 0 0 auto; font-size: 0.7rem; font-weight: 700; padding: 1px 7px; border-radius: 999px;
  background: var(--accent-soft); color: var(--accent-strong); }
.asb-devis-recap.is-active .asb-devis-count { background: rgba(255,255,255,0.22); color: #fff; }
/* Numéro de pièce (pastille ronde) + désignation tronquée + coche « validée ». */
.adp-num { flex: 0 0 auto; width: 19px; height: 19px; display: inline-flex; align-items: center; justify-content: center;
  font-size: 0.7rem; font-weight: 700; border-radius: 50%; background: var(--accent-soft); color: var(--accent-strong); }
.asb-devis-piece.is-active .adp-num { background: rgba(255,255,255,0.22); color: #fff; }
.adp-desig { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.adp-ok { flex: 0 0 auto; color: #16a34a; font-weight: 700; font-size: 0.82rem; }
.asb-devis-piece.is-active .adp-ok { color: #86efac; }

/* Sous-onglets de PIÈCE : masqués tant qu'aucune pièce n'est ouverte (inbox/référentiels). */
body.admin-no-piece #adminTabs { display: none; }
/* (Phase 2b) « ← Retour » flottants retirés en admin : le fil d'Ariane assure la remontée. */
body.view-admin .btn-back { display: none; }

@media (max-width: 860px) {
  body.view-admin .admin-layout { flex-direction: column; gap: 12px; }
  .admin-sidebar { position: static; flex: none; width: 100%; flex-direction: row; flex-wrap: wrap; gap: 8px 16px; padding: 12px 14px; }
  .asb-group { flex-direction: row; flex-wrap: wrap; align-items: center; gap: 6px; padding: 0; }
  .asb-group + .asb-group { border-top: none; }   /* (v4.69) pas de trait horizontal en disposition « en ligne » (mobile) */
  .asb-label { margin: 0; width: 100%; }
}

.admin-tabs { display: flex; gap: 3px; margin: 0 0 18px; background: var(--card); border: 1px solid var(--border); border-radius: var(--r); padding: 4px; box-shadow: var(--shadow-sm); }
.admin-tab { flex: 0 1 auto; border: none; background: transparent; color: var(--muted); font-family: inherit; font-size: 0.9rem; font-weight: 600; padding: 9px 14px; border-radius: var(--r-sm); cursor: pointer; transition: background 0.15s, color 0.15s; }
/* Barre grise séparant les onglets PAR PIÈCE (gauche) des onglets COMMUNS (droite, poussés via margin auto) — v1.95. */
.tab-sep { flex: 0 0 1px; align-self: stretch; background: var(--border); margin: 3px 7px 3px auto; }
.admin-tab:hover { background: var(--surface-2); color: var(--text); }
.admin-tab.is-active { background: var(--accent); color: #fff; }

/* Chaque onglet n'affiche que ses cartes (data-atab) ; les autres sont masquées. La
   sélection (type/attachement) n'est pas taguée → reste visible au-dessus, quel que soit
   l'onglet. L'alignement JS des schémas est désactivé en admin (colonnes indépendantes). */
/* …sauf les BOUTONS d'onglet (qui portent eux aussi data-atab) → :not(.admin-tab). */
body.view-admin.atab-piece       [data-atab]:not([data-atab="piece"]):not(.admin-tab),
body.view-admin.atab-fabrication  [data-atab]:not([data-atab="fabrication"]):not(.admin-tab),
body.view-admin.atab-devis        [data-atab]:not([data-atab="devis"]):not(.admin-tab),
body.view-admin.atab-parametres   [data-atab]:not([data-atab="parametres"]):not(.admin-tab),
body.view-admin.atab-catalogue    [data-atab]:not([data-atab="catalogue"]):not(.admin-tab),
body.view-admin.atab-prix         [data-atab]:not([data-atab="prix"]):not(.admin-tab),
body.view-admin.atab-gamme        [data-atab]:not([data-atab="gamme"]):not(.admin-tab) { display: none !important; }
/* (v4.07) La bannière « Sélection » (type/attachement de la pièce) n'est PAS taguée data-atab
   → elle restait visible dans l'onglet Catalogue, qui a pourtant SES PROPRES sélecteurs en
   haut du tableau (retour utilisateur). On la masque sur cet onglet. */
body.view-admin.atab-catalogue .selection-row { display: none !important; }

/* La bannière « Sélection » (type/attachement) est un CONTEXTE de pièce : utile dans Pièce /
   Fabrication / Devis / Catalogue (qui dépendent du type/attachement), inutile dans Paramètres /
   Prix acier / Gamme (réglages globaux) → on l'y masque. */
body.view-admin.atab-parametres .selection-row,
body.view-admin.atab-prix        .selection-row,
body.view-admin.atab-gamme       .selection-row { display: none; }
body.view-admin .schema-stack { position: static; }
body.view-admin .card[data-atab], body.view-admin .schema-stack[data-atab] { margin-bottom: 16px; }

/* Onglet PIÈCE : schéma technique à GAUCHE, bannière des cotes à DROITE (2 colonnes —
   on occupe enfin l'espace horizontal). align-items:stretch → la colonne de gauche épouse
   la HAUTEUR de la bannière des cotes ; schéma technique + schéma des options se partagent
   cette hauteur à parts ÉGALES (flex:1 chacun), images redimensionnées (object-fit:contain). */
body.view-admin.atab-piece .layout { display: flex; flex-wrap: wrap; gap: 20px; align-items: stretch; }
body.view-admin.atab-piece .layout > .left-stack { flex: 0 0 360px; max-width: 360px; display: flex; flex-direction: column; }
/* flex-basis 0 (et non `auto`) — CRUCIAL (v1.91) : avec `auto`, la base du milieu = la largeur du CONTENU
   (bannière des cotes, large) ; sur un écran un peu juste, left(360) + mid(contenu) dépassait la fenêtre et
   le milieu PASSAIT À LA LIGNE → les schémas se retrouvaient AU-DESSUS des dimensions. En base 0, le milieu
   tient toujours à côté des schémas (il grandit dans l'espace restant) ; seule la bannière Couple (base 100%)
   passe à la ligne suivante. Les schémas restent donc TOUJOURS sur le côté (cf. téléphone : @media 720px). */
body.view-admin.atab-piece .layout > .mid-stack { flex: 1 1 0; min-width: 0; }
body.view-admin.atab-piece .layout > .results-stack { display: none; }
/* La colonne de gauche (schémas) ne s'étire QUE sur la rangée schéma|cotes (1re ligne du
   flex-wrap) → les schémas font pile la hauteur de la bannière des cotes. */
/* La pile de schémas REMPLIT la colonne de gauche (= hauteur de la bannière des cotes) ; les
   schémas DÉROULÉS se partagent la hauteur restante (flex:1 chacun → plus ou moins grands selon
   le nombre déroulé), les repliés restent des barres fines (v2.12). */
body.view-admin.atab-piece #schemaCol { flex: 1 1 auto; height: auto; min-height: 0; gap: 12px; margin-bottom: 0; display: flex; flex-direction: column; }
body.view-admin.atab-piece #schemaCol > .schema-col { max-width: 360px; }
/* Bannière COUPLE : passe à la LIGNE SUIVANTE (flex-basis 100%) et occupe TOUTE la largeur,
   sous la rangée schéma|cotes. */
body.view-admin.atab-piece .layout > #cardCouple { flex: 1 1 100%; max-width: 100%; }

/* ── Schémas MINIMISÉS (admin · onglet Pièce, v2.04) : repliés par défaut (barre-titre cliquable),
   agrandis au clic. Libère la hauteur de la colonne de gauche → Dimensions devient la plus haute,
   la bannière Couple se colle dessous (plus de trou Dimensions↔Couple). ── */
.schema-min-head { display: none; }
body.view-admin.atab-piece .schema-col > .schema-min-head {
  display: flex; align-items: center; justify-content: flex-start; gap: 7px; width: 100%;
  font: inherit; font-size: 0.82rem; font-weight: 600; color: var(--text);
  background: none; border: none; cursor: pointer; padding: 4px 36px 4px 6px; border-radius: var(--r-sm);
  transition: background .12s, color .12s;   /* flèche À GAUCHE devant le titre ; padding-droit réservé à l'icône ⛶ */
}
body.view-admin.atab-piece .schema-col > .schema-min-head:hover { background: var(--accent-soft); color: var(--accent-strong); }
body.view-admin.atab-piece .schema-col > .schema-min-head:hover .smh-chevron { color: var(--accent-strong); }
/* Flèche bien visible (accent) = repère « cliquable pour agrandir/replier ». */
body.view-admin.atab-piece .schema-col > .schema-min-head .smh-chevron { color: var(--accent); font-size: 0.95rem; line-height: 1; flex: 0 0 auto; }
/* Schéma DÉROULÉ : la barre-titre grise est COLLÉE en haut-gauche du cadre et s'arrête AVANT
   l'icône plein écran (coin haut-droit) — pas de chevauchement (v2.15). */
body.view-admin.atab-piece .schema-col.schema-open > .schema-min-head {
  flex: 0 0 auto; background: var(--surface-2); align-self: flex-start; width: auto; max-width: calc(100% - 40px);
  margin: 0 0 6px 0; padding: 7px 12px; border-radius: 0 0 var(--r-sm) 0;
}
/* Replié : la boîte se réduit à la barre, image + bouton plein écran masqués. */
body.view-admin.atab-piece .schema-col:not(.schema-open) {
  flex: 0 0 auto; height: auto !important; min-height: 0 !important; display: block;
  background-color: var(--surface-2); background-image: none; padding: 9px 12px;
}
body.view-admin.atab-piece .schema-col:not(.schema-open) > img,
body.view-admin.atab-piece .schema-col:not(.schema-open) > .schema-title,
body.view-admin.atab-piece .schema-col:not(.schema-open) > .schema-fs-btn { display: none !important; }   /* !important : bat #planRealiseImg (ID) */
body.view-admin.atab-piece .schema-col:not(.schema-open)::after { content: none !important; }              /* pas de chip légende sur la barre repliée */
/* La barre grise (schema-min-head) porte déjà le titre du plan → on masque le titre blanc
   redondant (#planRealiseTitle) en admin (v2.16). */
body.view-admin.atab-piece .schema-col > .schema-title { display: none !important; }
/* Déroulé : barre en haut + image en dessous ; partage la hauteur restante de la colonne.
   padding:0 sur la boîte → la barre grise se colle au cadre ; l'image porte sa propre marge. */
body.view-admin.atab-piece .schema-col.schema-open { display: flex; flex-direction: column; flex: 1 1 0; min-height: 130px; height: auto; padding: 0; }
body.view-admin.atab-piece .schema-col.schema-open > img { flex: 1 1 auto; min-height: 0; width: 100%; object-fit: contain; padding: 2px 16px 16px; }
/* Dimensions occupe la colonne du milieu → la bannière Couple se colle dessous (v2.04). La
   marge-bas de carte (16px) est redondante avec le gap flex de la rangée → retirée pour ne
   garder qu'UN espacement (le gap de 20px), comme entre les autres bannières. */
body.view-admin.atab-piece .mid-stack > #cardDimensions { flex: 1 1 auto; margin-bottom: 0; }

/* Onglets FABRICATION / DEVIS / PARAMÈTRES : une seule colonne lisible. */
body.view-admin:not(.atab-piece) .layout { display: block; }
body.view-admin:not(.atab-piece) .layout > .left-stack,
body.view-admin:not(.atab-piece) .layout > .mid-stack,
body.view-admin:not(.atab-piece) .layout > .results-stack { display: block; width: auto; flex: none; min-width: 0; }
body.view-admin:not(.atab-piece) .card { max-width: none; }   /* tous les onglets : pleine largeur */

/* ── Onglet Usinage — ENCOMBREMENT (v5.06) : carte à la grille 2 colonnes STANDARD des cartes
   admin (Longueur|Largeur, Hauteur|Volume, coût matière pleine largeur), cohérente avec les
   autres onglets. La formule d'un champ s'affiche SOUS son champ quand on déplie sa flèche ▸
   (.formula-open) — le champ ouvert prend alors toute la largeur (règle .formula-open commune
   plus bas). Remplace l'ancienne mise en page « valeur | formule » côte à côte liée à la case
   globale « Voir les formules » (retirée v5.06). ── */
body.view-admin #cardRealisation .formula-edit { display: none; align-items: center; gap: 8px; margin: 6px 0 0; }
body.view-admin #cardRealisation .field.formula-open .formula-edit:not([hidden]) { display: flex; }
/* (v5.10) Libellé/champ : tailles de la base COMMUNE des formules (plus de dérogation locale). */
body.view-admin #cardRealisation .formula-label { margin: 0; flex: 0 0 auto; white-space: nowrap; }
body.view-admin #cardRealisation .formula-input { flex: 1 1 auto; min-width: 0; }
/* ── (v5.08) Onglet Usinage ALIGNÉ sur le RYTHME de l'onglet Outil (demande utilisateur :
   formats cohérents entre tous les onglets). L'onglet héritait du gabarit générique des cartes
   admin (labels 0.78rem, champs 0.85rem/5px 8px) alors que l'onglet Outil (#cardDimensions)
   utilise labels 0.82rem + champs 0.9rem/6px 9px ; et ses cartes s'étiraient sur toute la
   largeur de page (champs géants). On aligne tout : ── */
/* (v5.10) Tout l'espace horizontal OCCUPÉ (demande utilisateur) : les deux cartes de l'onglet
   (Encombrement | Usinage) se partagent la LARGEUR côte à côte au lieu de s'empiler bornées à
   760 px (v5.08) — chaque carte garde ainsi des champs à taille raisonnable SANS espace mort.
   Les autres cartes de la pile (masquées par le filtre data-atab) ne participent pas au flex. */
/* (v5.14) CORRECTIF : `flex-direction: row` EXPLICITE — la règle de base .results-stack est en
   COLONNE (pile de l'onglet Outil) : sans lui, les cartes restaient EMPILÉES et, avec
   align-items:flex-start en colonne, RÉTRÉCIES à leur contenu (~la moitié de la page vide). */
body.view-admin.atab-fabrication .results-stack { display: flex !important; flex-direction: row; gap: 20px; align-items: flex-start; }
body.view-admin.atab-fabrication .results-stack > .card { flex: 1 1 0; min-width: 0; max-width: none; margin-bottom: 0; }
@media (max-width: 1000px) {   /* écrans étroits : retour à l'empilement */
  body.view-admin.atab-fabrication .results-stack { display: block !important; }
  body.view-admin.atab-fabrication .results-stack > .card { margin-bottom: 20px; }
}
/* Même typographie de champs que l'onglet Outil. (v5.17 : étendue à l'onglet DEVIS, resté au
   petit gabarit générique — 13.6px/12.48 mesurés — comme l'était Usinage avant la v5.08.) */
body.view-admin.atab-fabrication .card label,
body.view-admin.atab-devis .card label { font-size: 0.82rem; }
/* (:not() nécessaires pour BATTRE la règle générique des cartes admin, plus bas dans le
   fichier, à spécificité sinon égale. Les .poste-input du devis gardent leur style propre.) */
body.view-admin.atab-fabrication .card input[type="number"]:not(.poste-input),
body.view-admin.atab-fabrication .card input[type="text"]:not(.formula-input):not(.poste-input),
body.view-admin.atab-devis .card input[type="number"]:not(.poste-input),
body.view-admin.atab-devis .card input[type="text"]:not(.formula-input):not(.poste-input) {
  padding: 6px 9px;
  font-size: 0.9rem;
}
/* Même rythme vertical que l'onglet Outil (marge de champ unique, pas gap + marge cumulés). */
/* (v5.13) Le rythme vertical (gap 0 + marge de champ 9px) est désormais celui de TOUTES les
   cartes admin (règle générique plus bas) — les dérogations locales v5.08/v5.10 sont retirées. */
/* « Coût matière brut » : il portait le style BADGE client (1.05rem gras) qui détonait — en
   admin il reprend la taille des autres champs (le fond bleuté discret reste : c'est un résultat). */
body.view-admin #coutMatiere { font-size: 0.9rem; font-weight: 600; }
/* « Nombre de poses » : aligné sur la largeur d'UNE colonne de la grille (comme Longueur/Largeur
   au-dessus) au lieu d'un 170px arbitraire — la colonne de champs reste régulière. */
body.view-admin #cardUsinage #nombrePoses { max-width: calc(50% - 7px); }

/* Grilles des cartes : 2 colonnes ; un champ DÉPLIÉ (.formula-open) reprend toute la largeur
   (la formule reste lisible sous son champ). (v5.06) plus de variante body.formules-on.
   (v5.13) RYTHME VERTICAL RÉGULIER (demande utilisateur) : l'espacement entre champs vient
   d'UNE SEULE source — la marge de champ (9px, celle de l'onglet Outil) ; le row-gap passe à 0
   (avant : gap 8px + marge 8px se CUMULAIENT → ~16px effectifs, presque le double de l'onglet
   Outil, d'où des espaces inégaux d'un onglet à l'autre). */
body.view-admin .card .grid {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 14px;
}
body.view-admin .grid > .field.formula-open:has(.formula-edit:not([hidden])) { grid-column: 1 / -1; }

/* Champs et libellés réduits (les champs de formule/poste gardent leur style propre). */
body.view-admin .card label {
  font-size: 0.78rem;
  margin-bottom: 3px;
}
body.view-admin .card select,
body.view-admin .card input[type="number"]:not(.poste-input),
body.view-admin .card input[type="text"]:not(.formula-input) {
  padding: 5px 8px;
  font-size: 0.85rem;
}
body.view-admin .field { margin-bottom: 9px; }   /* (v5.13) = rythme de l'onglet Outil, partout */
body.view-admin .card { --card-pad: 18px; padding: var(--card-pad); }   /* (v3.72) le bandeau suit via la variable */
body.view-admin .card h2 { margin-bottom: 12px; padding-bottom: 7px; }
body.view-admin .card > h2 { margin-bottom: 14px; padding-top: 10px; padding-bottom: 9px; }   /* bandeau compact (densité admin) */

/* Carte Dimensions FUSIONNÉE (cotes + options — la grille des Options y est
   déplacée par setView, la carte Options vidée est masquée) : liste DENSE, sans
   les titres « Dimensions » / « Options supplémentaires » — l'admin se repère
   aux schémas. 3 colonnes, sections compactées, schéma des options réduit. */
body.view-admin #cardOptions { display: none; }
body.view-admin #cardDimensions > h2 { display: none; }
body.view-admin #cardDimensions { padding: 16px 18px; }
/* Cotes UNE PAR LIGNE (v1.43-3) : un champ par ligne, lisible (fini la grille 3 colonnes
   serrée). On a la place : la bannière est désormais à droite du schéma. */
body.view-admin #cardDimensions .grid {
  grid-template-columns: 1fr;
  gap: 0 0;
}
body.view-admin #cardDimensions label { font-size: 0.82rem; margin-bottom: 3px; }
/* (v5.13) marge de champ : la règle générique des cartes admin (9px) suffit — dérogation retirée. */
body.view-admin #cardDimensions input[type="number"],
body.view-admin #cardDimensions input[type="text"]:not(.formula-input) {
  padding: 6px 9px;
  font-size: 0.9rem;
}
body.view-admin #cardDimensions .section-divider {
  font-size: 0.78rem;
  margin-top: 4px;
  padding-top: 10px;
}
body.view-admin .schema-col--options { height: 170px; }

/* La section repliable « Taille de l'attachement » (C/D + flèche ▸) est masquée en admin :
   C et D sont rappelés dans l'en-tête #attachHead.
   (v5.21) EXCEPTION — attachement PERSONNALISÉ (tenon, body.attach-perso) : la section
   S'AFFICHE (partie « Attachement » de l'onglet Outil, demande utilisateur) au GABARIT des
   cotes (libellé | valeur client | ajustement), et remplace la ligne attach-head. Le carré
   perso et les attachements standard gardent l'attach-head une ligne. */
body.view-admin #sectionAttachement { display: none; }
body.view-admin.attach-perso #sectionAttachement { display: block; }
body.view-admin.attach-perso .attach-head { display: none !important; }
/* Champs C / D / Longueur de queue au gabarit des cotes de l'onglet Outil. */
body.view-admin #sectionAttachement > .field {
  display: grid; grid-template-columns: 82px minmax(0, 1fr) minmax(0, 1fr);
  column-gap: 22px; row-gap: 4px; align-items: start;
}
body.view-admin #sectionAttachement > .field > label { grid-column: 1; align-self: start; padding-top: 7px; justify-self: end; text-align: right; }
body.view-admin #sectionAttachement > .field > .client-cell { grid-column: 2; align-self: start; }
body.view-admin #sectionAttachement > .field > .input-unit { grid-column: 3; }

/* En-tête ATTACHEMENT (vue admin) : ligne discrète « Attachement · C = 12 · D = 9 mm ». */
.attach-head { display: none; }
body.view-admin .attach-head:not(:empty) {
  display: block;
  font-size: 0.82rem;
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: 6px 10px;
  margin-bottom: 12px;
}
.attach-head .ah-k { color: var(--muted); font-weight: 600; margin-right: 8px; }
.attach-head b { font-weight: 700; font-variant-numeric: tabular-nums; }
.attach-head .ah-sep { color: var(--muted); margin: 0 7px; }
.attach-head .ah-u { color: var(--muted); margin-left: 5px; }

/* ════════════════════════════════════════════════════════════════════════════
   PROVENANCE — DEUX COLONNES (v1.44) : chaque cote en admin = libellé | VALEUR
   CLIENT (lecture seule + mention auto/manuelle) | champ ADMIN éditable (test).
   ════════════════════════════════════════════════════════════════════════════ */
/* (v5.15) ÉCARTS HARMONISÉS (demande utilisateur) : les libellés de cote sont ALIGNÉS À DROITE
   de leur colonne → un écart IDENTIQUE (le column-gap) entre chaque libellé et son champ, quelle
   que soit la longueur du libellé (A vs Empreinte vs manche — fini le « gros espace » après un
   libellé court) ; et le column-gap passe de 12 à 22px (l'écart valeur client ↔ ajustements
   était, lui, trop petit). Même gabarit pour l'en-tête de colonnes et le panneau tuyauter. */
body.view-admin #cardDimensions .dim-colhead {
  display: grid; grid-template-columns: 82px minmax(0, 1fr) minmax(0, 1fr);   /* 1re colonne élargie pour les libellés du manche (v2.30) */
  column-gap: 22px; align-items: end;
  font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
  color: var(--muted); margin: 2px 0 8px;
}
body.view-admin #cardDimensions .grid > .field {
  display: grid; grid-template-columns: 82px minmax(0, 1fr) minmax(0, 1fr);
  column-gap: 22px; row-gap: 4px; align-items: start;   /* v2.44 : aligne la valeur client (col. 2) et le champ d'ajustement (col. 3) EN HAUT (la note de complétion ne décale plus le champ) */
}
body.view-admin #cardDimensions .grid > .field > label { grid-column: 1; align-self: start; padding-top: 7px; justify-self: end; text-align: right; }
body.view-admin #cardDimensions .grid > .field > .client-cell { grid-column: 2; align-self: start; }
body.view-admin #cardDimensions .grid > .field > .input-unit,
body.view-admin #cardDimensions .grid > .field > .cote-col { grid-column: 3; }
/* Champs du MANCHE (clés, v2.30) : imbriqués dans #sectionManche (pas enfants directs de .grid)
   → on leur applique la MÊME grille 3 colonnes que les cotes (libellé court À GAUCHE, puis
   « Valeur client » et « Ajustements »), pour un alignement parfait avec les cotes. */
body.view-admin #cardDimensions #sectionManche > .field {
  display: grid; grid-template-columns: 82px minmax(0, 1fr) minmax(0, 1fr);
  column-gap: 22px; row-gap: 4px; align-items: start;   /* v2.44 : alignement en haut (cf. cotes) ; (v5.15) gap 22 harmonisé */
}
body.view-admin #cardDimensions #sectionManche > .field > label { grid-column: 1; align-self: start; padding-top: 7px; justify-self: end; text-align: right; }
body.view-admin #cardDimensions #sectionManche > .field > .client-cell { grid-column: 2; align-self: start; }
body.view-admin #cardDimensions #sectionManche > .field > .input-unit { grid-column: 3; }

/* Poignée plastique (v2.32) : en CLIENT, case à cocher inline « ☐ Poignée plastique » (label
   cliquable). En admin, elle suit la grille du manche (ligne Oui/Non + case d'ajustement). */
/* CLIENT (v2.46) : la poignée est une OPTION PRODUIT (pas un réglage d'affichage comme
   « Personnaliser ») → présentée dans une BOÎTE distincte (bordure + fond léger, surlignée
   en accent quand cochée), avec un sous-texte. Case à droite, centrée verticalement. */
body.view-client #fieldPoignee {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  column-gap: 14px; row-gap: 2px;
  margin-top: 16px;
  padding: 11px 14px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  cursor: pointer;
}
body.view-client #fieldPoignee:has(input:checked) { border-color: var(--accent-line); background: var(--accent-soft); }
body.view-client #fieldPoignee > label { grid-column: 1; grid-row: 1; margin: 0; font-weight: 600; font-size: 0.95rem; color: var(--text); cursor: pointer; }
body.view-client #fieldPoignee .poignee-hint { grid-column: 1; grid-row: 2; font-size: 0.78rem; color: var(--muted); line-height: 1.3; }
body.view-client #fieldPoignee .poignee-control { grid-column: 2; grid-row: 1 / span 2; align-self: center; }
.poignee-control { display: inline-flex; align-items: center; }
.poignee-control > input { width: 18px; height: 18px; margin: 0; accent-color: var(--accent); cursor: pointer; }
/* Admin (v2.44) : la case CLIENT est masquée ; l'ajustement = case NUE (hors champ) + libellé
   = inverse du choix client, en colonne 3 (aligné en haut comme les autres ajustements). */
body.view-admin #fieldPoignee .poignee-control { display: none; }
body.view-admin #fieldPoignee .poignee-adj { grid-column: 3; align-self: start; padding-top: 6px; display: inline-flex; align-items: center; gap: 8px; cursor: pointer; }
.poignee-adj > input { width: 16px; height: 16px; cursor: pointer; }
.poignee-adj-lbl { font-weight: 600; color: var(--text); }

/* Mention « marge de manœuvre autour de l'écrou » (v2.32) — informative, non éditable. */
.maneuver-note { font-size: 0.82rem; color: var(--accent-strong); background: var(--accent-soft);
  border: 1px solid var(--accent-line); border-radius: var(--r-sm); padding: 7px 11px; line-height: 1.45; margin: 4px 0; }
/* Dégagement pour manœuvre : mention retirée de l'interface (client v2.81, admin v2.83). */
#maneuverNote { display: none !important; }
/* Notes / jauge / erreurs : pleine largeur sous la ligne. */
body.view-admin #cardDimensions .grid > .field > .auto-note,
body.view-admin #cardDimensions .grid > .field > .couple-verdict,
body.view-admin #cardDimensions .grid > .field > .field-error,
body.view-admin #cardDimensions .grid > .field > .field-warn,
body.view-admin #cardDimensions .grid > .field > .dim-note { grid-column: 1 / -1; }

/* Formules des COTES : sous le champ d'AJUSTEMENT (colonne 3), pas sous la valeur client.
   Le module COUPLE (3 modes RdM) garde toute la largeur. */
body.view-admin #cardDimensions .grid > .field:not(.couple-field) > .formula-edit { grid-column: 3; }
body.view-admin #cardDimensions .grid > .couple-field > .formula-edit { grid-column: 1 / -1; }

/* N'afficher la formule d'une cote QUE si elle est UTILISÉE (.formula-used posé par
   updateFormulaUsage = la cote découle de la formule) ET que le champ est DÉPLIÉ (.formula-open,
   flèche ▸). Sinon masquée. Le module Couple (toujours calculé) n'est pas concerné. (v5.06) */
body.view-admin #cardDimensions .field.formula-used.formula-open .formula-edit:not([hidden]) { display: flex; }
body.view-admin #cardDimensions .field:not(.couple-field):not(.formula-used) .formula-edit { display: none !important; }

/* ── Panneau de PROVENANCE d'une cote (flèche ▸, admin onglet Pièce, v1.99) ──
   (v5.11) Même GABARIT que les encarts de formule (mêmes fond/liseré acier/rayon) : les deux
   dépliables de la flèche ▸ parlent le même langage visuel. */
.field-prov { display: none; margin-top: 6px; padding: 8px 12px; background: #f7f9fc; border: 1px solid var(--line); border-left: 3px solid var(--brand-steel); border-radius: 8px; font-size: 0.82rem; line-height: 1.5; }
body.view-admin .prov-field.formula-open > .field-prov { display: block; }
/* (v5.22) La bannière de provenance démarre à la colonne « VALEUR CLIENT » (col. 2 — plus sous
   le libellé) et s'étend jusqu'au bord quand le champ n'a PAS de formule ; quand la FORMULE
   ÉDITABLE est dépliée à côté, elle se limite à la col. 2 → une colonne provenance | une
   colonne formule, alignées sur la même rangée (demande utilisateur). */
/* grid-row: 100 = RANGÉE PARTAGÉE dédiée en bas du champ (les rangées implicites vides entre
   les notes et celle-ci font 0 px) : panneau et formule restent côte à côte quel que soit le
   nombre de notes intercalées (une note pleine largeur les décalait sinon d'une rangée). */
body.view-admin #cardDimensions .grid > .field > .field-prov { grid-column: 2 / -1; grid-row: 100; align-self: start; }
body.view-admin #cardDimensions .grid > .field.formula-used.formula-open:has(> .formula-edit:not([hidden])) > .field-prov { grid-column: 2; }
body.view-admin #cardDimensions .grid > .field.formula-open > .formula-edit:not([hidden]) { grid-row: 100; align-self: start; margin-top: 6px; }
.field-prov .prov-how, .field-prov .prov-val,
.field-prov .prov-src, .field-prov .prov-formula { display: block; }   /* (v4.02) + source précise & formule */
.field-prov .prov-val { margin-top: 3px; color: var(--accent-strong); }
.field-prov .prov-src, .field-prov .prov-formula { margin-top: 3px; }
.field-prov .prov-formula { font-variant-numeric: tabular-nums; }
body.view-client .field-prov { display: none !important; }

/* Case VALEUR CLIENT (lecture seule) + mention de provenance dessous. */
.client-cell { display: none; }
body.view-admin #cardDimensions .client-cell { display: flex; flex-direction: column; gap: 1px; }
.client-cell .cc-val {
  font-size: 0.9rem; font-weight: 600; font-variant-numeric: tabular-nums; color: var(--text);
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r-sm);
  padding: 5px 9px; min-height: 1.5em; line-height: 1.4;
}
.client-cell .cc-val:empty::before { content: '—'; color: var(--muted); font-weight: 400; }
.client-cell .cc-prov { font-size: 0.64rem; color: var(--muted); line-height: 1.2; min-height: 0.85em; }
/* (v3.64) Provenance en PASTILLE compacte « auto » / « manuel » (les mentions en toutes lettres
   alourdissaient chaque ligne) — détail complet en infobulle ; ton NEUTRE (doctrine v1.44 :
   pas de code couleur). */
.client-cell .cc-prov--chip { display: inline-block; width: max-content; border: 1px solid var(--border);
  background: var(--surface-2); border-radius: 999px; padding: 1px 7px; font-weight: 600;
  letter-spacing: 0.02em; cursor: help; }

/* La colonne « valeur client » remplace le rappel « = X » du libellé → masqué ici. */
body.view-admin #cardDimensions .label-val { display: none; }

/* Rappel NOIR de la valeur retenue, à côté du libellé (rempli par
   updateLabelMirrors) : ce que le client a saisi — ou laissé tel quel après
   autocomplétion — se lit d'un coup d'œil, même quand le champ affiche sa
   provenance en gris/orange. */
.label-val { display: none; }
body.view-admin .label-val {
  display: inline;
  margin-left: 5px;
  color: var(--text);
  font-weight: 600;
}
body.view-admin .label-val:empty { display: none; }

/* Flèche ▸ d'affichage des formules / du détail de provenance d'un champ (insérée par app.js
   dans le libellé ; visible uniquement en vue admin ET si le champ porte au moins une formule
   applicable au contexte — :has — ou une provenance). Pivote quand le champ est déplié.
   (v5.12) PLUS VISIBLE (demande utilisateur) : taille montée (0.72 → 0.95rem), couleur acier
   de charte au lieu du gris pâle, survol accentué. La pastille « détail » des cotes (v4.02-04)
   est RETIRÉE : même flèche partout, alignée dans le libellé. */
.formula-toggle {
  display: none;
  background: none;
  border: none;
  cursor: pointer;
  padding: 0 5px;
  margin-left: 3px;
  font-size: 0.95rem;
  line-height: 1;
  color: var(--brand-steel);
  transition: transform 0.15s, color 0.12s;
}
.formula-toggle:hover { color: var(--accent-strong); }
body.view-admin .field:has(.formula-edit:not([hidden])) .formula-toggle { display: inline-block; }
/* Cotes d'ajustement (A/B/H/S/P) : la flèche de PROVENANCE est toujours offerte en admin (v1.99). */
body.view-admin .prov-field > label .formula-toggle { display: inline-block; }
/* (v5.06) La flèche pivote quand SON champ est déplié (.formula-open) — plus de flag global. */
.formula-open .formula-toggle { transform: rotate(90deg); }
/* (v5.19) La flèche est HORS FLUX (absolue à sa position NATURELLE — tous les offsets restent
   auto) : elle ne compte plus dans la largeur de la ligne du libellé → elle ne peut plus passer
   À LA LIGNE et décaler les champs (régression v5.12 : la flèche agrandie faisait déborder les
   libellés longs, ex. « Longueur de l'encombrement (mm) »). Un libellé qui frôle le bord de
   colonne laisse la flèche flotter dans l'inter-colonne (gap 14-22 px — elle y tient). */
body.view-admin .field > label { position: relative; }
body.view-admin .field > label > .formula-toggle { position: absolute; margin-left: 2px; padding: 0 3px; }

/* Répartition des colonnes (v0.81) : schémas rétrécis (l'admin connaît la pièce),
   la colonne du MILIEU reçoit la carte fusionnée + Variables (saisies et
   paramètres), la DROITE garde Encombrement + Usinage (résultats calculés). */
body.view-admin .layout > .left-stack { flex: 0 0 220px; }
body.view-admin .general-schema { flex: 0 0 280px; height: 200px; }

/* ── Mise en page deux colonnes ── */
.layout {
  display: flex;
  gap: 24px;
  align-items: flex-start;
}

/* Rangée sous la Sélection : 3 colonnes égales — colonne de gauche (schémas, +
   Variables en admin) | pile du milieu (carte « Dimensions ») | pile de droite
   (Encombrement / Usinage / Variables en client). */
.layout > .left-stack,
.layout > .card,
.layout > .mid-stack,
.layout > .results-stack {
  flex: 1 1 0;
  min-width: 0;
}
/* Conteneur de la colonne de gauche : empile la pile de schémas, puis (admin)
   la carte Variables, verticalement. */
.left-stack {
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.left-stack > .card { margin-bottom: 0; }

/* Pile du MILIEU : la carte Dimensions ; en vue admin, setView y empile aussi
   la carte Variables (même gabarit que la pile de droite). */
.mid-stack {
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.mid-stack > .card { margin-bottom: 0; }

/* Pile de droite : trois cartes empilées (hauteur totale ≈ carte Dimensions).
   La carte Variables, en bas de pile, est le niveau « expert ». */
.results-stack {
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.results-stack > .card { margin-bottom: 0; }

/* Carte « expert » (Variables) : titre atténué — paramètres avancés, hors du
   flux principal de lecture. */
.card--expert h2 {
  color: var(--muted);
  font-size: 0.95rem;
}

/* 1re colonne : pile verticale (forme d'attachement + schéma technique + options).
   position:relative = ancrage des boîtes que le JS aligne sur les sections de la
   carte « Caractéristiques » (elles défilent AVEC leur section ; plus de figement
   sticky). En mise en page mobile, la colonne repasse en static → alignement JS
   désactivé, flux naturel (voir media query plus bas). */
.schema-stack {
  display: flex;
  flex-direction: column;
  gap: 20px;
  position: relative;
}

/* Une boîte de schéma MASQUÉE (attribut hidden posé par le JS) doit disparaître vraiment —
   sinon `.schema-col{display:flex}` la laisse en cadre vide (ex. plan absent). (v2.83) */
.schema-col[hidden] { display: none !important; }

/* Boîte de schéma (technique et options partagent le même cadre).
   position:relative = ancrage de la légende ::after (sans effet quand le JS
   d'alignement pose position:absolute en inline, qui ancre tout autant). */
/* Cadre « blueprint » : fond blanc quadrillé léger + bordure nette (fini le look
   « image cassée » des pointillés). Le schéma (PNG en multiply) se pose dessus. */
.schema-col {
  height: 460px;
  background-color: #fbfcff;
  background-image:
    linear-gradient(var(--line) 1px, transparent 1px),
    linear-gradient(90deg, var(--line) 1px, transparent 1px);
  background-size: 18px 18px;
  background-position: -1px -1px;
  border: 1px solid var(--border);
  border-radius: var(--r);
  box-shadow: var(--shadow-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 18px 18px 34px;   /* marge basse : réserve la place de la légende-chip */
  overflow: hidden;
  position: relative;
}

/* Légende sous chaque schéma, en PASTILLE (aide à la lecture pour les non-initiés) */
.schema-col::after {
  position: absolute;
  left: 50%;
  bottom: 9px;
  transform: translateX(-50%);
  max-width: calc(100% - 24px);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  background: rgba(255,255,255,0.92);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 2px 11px;
  font-size: 0.72rem;
  font-style: normal;
  color: var(--muted);
  content: "";
  pointer-events: none;
}
.schema-col--attach::after  { content: "Forme de l'attachement"; }
#schemaTechBox::after       { content: "Schéma des cotes de la pièce"; }
.schema-col--options::after { content: "Options supplémentaires"; }
.general-schema::after      { content: "Schéma général de l'embout"; }

/* Schéma de la forme d'attachement : boîte plus courte, au-dessus du schéma technique */
.schema-col--attach {
  height: 220px;
}

/* Schéma des options : boîte plus courte, sous le schéma technique */
.schema-col--options {
  height: 240px;
}
/* Schéma des options DANS la carte Options (pleine largeur, sous les angles) :
   espace de respiration pour ne pas coller au champ γ (v1.77). */
.schema-col--options.field-full {
  margin-top: 14px;
}

/* Parcours « personnalisé » : pas de schéma technique (spécifique aux dimensions
   d'un attachement standard) → on masque sa boîte (l'alignement JS ignore une
   boîte masquée). */
.schema-stack--custom #schemaTechBox {
  display: none;
}

#imageAttachSchema,
#imageSchema,
#imageOptionsSchema,
#imageGeneralSchema {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  object-position: center;
  mix-blend-mode: multiply;
  transition: opacity 0.13s ease;   /* fondu au blanc lors d'un changement de schéma (v2.73) */
}

/* CLÉS (v3.25) : templates re-rendus À L'HORIZONTAL depuis les PDFs CAO (manche à DROITE,
   lettres à l'endroit — fini les rotations portrait v2.75/2.76). (v3.34) Le patron « la boîte
   ÉPOUSE l'image » est désormais GÉNÉRALISÉ à tous les types dans le bloc « pile de schémas
   collante » — plus de règles spécifiques is-cle ici. */

/* Rangée du haut : carte « Sélection » (raccourcie) + schéma général de l'embout à droite */
.selection-row {
  display: flex;
  gap: 24px;
  align-items: stretch;     /* la boîte schéma épouse la hauteur de la carte */
  margin-bottom: 20px;
}
.selection-row > #cardSelection {
  flex: 1 1 0;
  margin-bottom: 0;          /* l'espacement vient de .selection-row */
}
/* Boîte du schéma général : à droite de la Sélection, hauteur fixe (image contenue).
   Avec align-items:stretch, la carte (hauteur auto) s'aligne sur cette hauteur. */
.general-schema {
  flex: 1 1 0;
  height: 240px;             /* override la hauteur fixe de .schema-col (460px) */
}

/* Colonne droite : formulaire */
.form-col {
  flex: 1;
  min-width: 0;
}

/* ── Cartes ── */
.card {
  --card-pad: 24px;   /* (v3.72) padding en variable : le BANDEAU de titre s'y ancre (marges négatives) */
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: var(--card-pad);
  margin-bottom: 20px;
  box-shadow: var(--shadow);
}

.card h2 {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 1.05rem;          /* (v3.72) un cran sous l'ancien 1.14 : le bandeau porte déjà la hiérarchie */
  font-weight: 700;
  letter-spacing: 0.01em;
  color: var(--brand-navy);   /* titres en marine de marque */
  margin: 0 0 18px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--line);
}
/* (v3.72) BANDEAU D'EN-TÊTE des cartes (revue design « pro ») : le titre ne flotte plus dans le
   blanc — il vit dans une bande gris très clair collée aux bords de la carte (marges négatives
   = -padding), séparée du corps par un filet. Donne le 3e niveau de surface (page / carte /
   bandeau) qui manquait. Seuls les h2 ENFANTS DIRECTS de la carte deviennent bandeau. */
.card > h2 {
  margin: calc(-1 * var(--card-pad)) calc(-1 * var(--card-pad)) 18px;
  padding: 12px var(--card-pad) 11px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  border-radius: calc(var(--r) - 1px) calc(var(--r) - 1px) 0 0;
}
/* Petit repère accentué devant chaque titre de carte (rappel du dégradé de marque). */
.card h2::before {
  content: "";
  flex: none;
  width: 4px;
  height: 15px;
  border-radius: 2px;
  background: var(--brand-gradient);
}
.card--expert h2::before { background: var(--muted); }
/* Mention « revue expert » (vue client) À DROITE du titre « Devis » (v2.53) : poussée à droite,
   plus petite/discrète que le titre, sans le détail des raisons (réservé aux résumés admin). */
.devis-expert-note { margin-left: auto; font-size: 0.78rem; font-weight: 600; color: var(--warn);
  line-height: 1.35; max-width: 68%; text-align: right; letter-spacing: 0; }
.devis-expert-note[hidden] { display: none; }

/* ── Champs ── */
.field { margin-bottom: 16px; }
.field:last-child { margin-bottom: 0; }

label {
  display: block;
  font-size: 0.85rem;
  font-weight: 500;
  margin-bottom: 6px;
}

select,
input[type="number"],
input[type="text"] {
  width: 100%;
  padding: 10px 12px;
  font-size: 0.95rem;
  font-family: inherit;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: #fff;
  color: var(--text);
  transition: border-color 0.15s, color 0.15s, box-shadow 0.15s, background 0.15s;
}
/* (v3.58) LISTE DÉROULANTE des <select> : le popup natif ne reprend pas le font-family:inherit
   du select (il retombe sur la police système) → Inter posée EXPLICITEMENT sur les <option>,
   avec la couleur d'encre du site et un peu d'air. Le popup n'accepte qu'un styling limité
   (font/couleur/fond/padding) — suffisant pour la cohérence. */
option, optgroup {
  font-family: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size: 0.95rem;
  color: var(--text);
  background: #fff;
  padding: 6px 10px;
}
optgroup { font-weight: 600; color: var(--muted); }

/* (v3.60) MENUS DÉROULANTS entièrement stylés (« customizable select », appearance: base-select —
   Chrome/Edge 135+) : même avec la v3.58, le popup reste un widget de l'OS qui ignore l'essentiel
   du CSS (police système, lignes tassées, surlignage bleu vif). En mode base-select le picker
   devient un élément de la page : Inter partout, surface blanche aux coins arrondis de la charte,
   ombre douce, lignes aérées, survol bleu clair, option choisie en marine + coche accent.
   Navigateurs sans support : @supports les laisse sur le popup natif (règle v3.58 ci-dessus).
   (Tous nos <select> sont simples — aucun multiple/size, compatible base-select.) */
@supports selector(::picker(select)) {
  select, ::picker(select) { appearance: base-select; }
  /* (v3.62) En base-select, le <select> fermé devient un conteneur FLEX avec align-items:normal
     (= stretch) : quand il est ÉTIRÉ par son parent (ex. sélecteur d'unité mm/″ de A, dans
     .input-unit en align-items:stretch), son contenu (« mm » + flèche) restait collé EN HAUT
     → centrage vertical, comme le faisait le rendu natif. */
  select { align-items: center; }
  ::picker(select) {
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
    background: #fff;
    box-shadow: 0 10px 28px rgba(1, 57, 95, 0.16);
    padding: 4px;
    margin-block: 4px;         /* petit écart entre le champ et la liste */
  }
  option {
    padding: 8px 10px;
    border-radius: 6px;
    transition: background 0.1s;
  }
  option:hover, option:focus { background: var(--accent-soft); }
  option:checked { color: var(--accent-strong); font-weight: 600; }
  option::checkmark { color: var(--accent); margin-inline-end: 6px; }
  select::picker-icon { color: var(--muted); }
}

/* Tableaux CATALOGUE & PRIX ACIER (v1.92) : cases RECTANGULAIRES (coins droits) et plus ÉTROITES.
   La règle de base ci-dessus (input[type=...] : width 100% + coins arrondis) a une spécificité (0,1,1)
   qui bat .cat-cell/.prix-cell (0,1,0) — on la rebat ici avec input.<classe> (même spécificité, placée
   APRÈS). Largeurs calées sur le pire cas « 9999,99 » (4 chiffres, virgule, 2 décimales). */
input.cat-cell, input.prix-cell, input.prix-lb, input.prix-ref { border-radius: 0; }
input.cat-cell  { width: 54px; }
/* (v4.06) Padding horizontal 8 → 5 px sur les cellules catalogue : la règle admin
   `body.view-admin .card input[type="number"]:not(.poste-input)` (padding 5px 8px, spéc. 0,4,2)
   ne laissait que ~38 px de texte dans 54 px — une valeur à décimales pleine (« 41,93 »)
   dépassait de ~2 px (dernier chiffre rogné). On la rebat via un sélecteur à ID (#cardCatalogue,
   spéc. 1,1,1), sans élargir la colonne (la grille contraint la largeur → l'élargir ferait
   déborder toutes les cellules). */
#cardCatalogue input.cat-cell { padding-left: 5px; padding-right: 5px; }
input.cat-cell--emp { width: 130px; }   /* champ EMPREINTE (texte) plus large (v1.93) */
/* (la largeur de .prix-cell est forcée via #cardPrixAcier plus haut — sélecteur ID prioritaire.) */
/* (v4.06) FLÈCHES natives (spin buttons) des inputs number RETIRÉES dans ces tables : les
   cellules sont étroites (54–76 px, calées sur « 9999,99 ») et le spinner — qui apparaît au
   survol/focus — RECOUVRAIT les derniers chiffres d'une valeur pleine (retour utilisateur).
   La saisie se fait au clavier ; la largeur entière reste lisible. Scopé à ces tables (les
   cotes du formulaire gardent leurs flèches). */
input.cat-cell::-webkit-outer-spin-button, input.cat-cell::-webkit-inner-spin-button,
input.prix-cell::-webkit-outer-spin-button, input.prix-cell::-webkit-inner-spin-button,
input.prix-lb::-webkit-outer-spin-button, input.prix-lb::-webkit-inner-spin-button {
  -webkit-appearance: none; appearance: none; margin: 0;
}
input.cat-cell, input.prix-cell, input.prix-lb { -moz-appearance: textfield; appearance: textfield; }

select:hover:not(:disabled),
input[type="number"]:hover:not([readonly]):not(:disabled),
input[type="text"]:hover:not([readonly]):not(:disabled) {
  border-color: #b7c1cf;
}

select:focus,
input[type="number"]:focus,
input[type="text"]:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

/* ── Accessibilité & finition (v1.77) ─────────────────────────────────────────
   Anneau de focus CLAVIER unifié (charte) sur les éléments interactifs qui n'en
   avaient pas (boutons, tuiles, onglets, cases, liens, résumés). Visible seulement
   au clavier grâce à :focus-visible → pas d'anneau au clic souris. Les champs
   texte/nombre gardent leur propre anneau (box-shadow ci-dessus). */
button:focus-visible,
a:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible,
.type-tile:focus-visible,
input[type="checkbox"]:focus-visible,
.prix-import-label:focus-within {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* Respect de la préférence « mouvement réduit » : on neutralise transitions/animations. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
}

/* Champ en lecture seule (C, D…) ou désactivé (P, β/γ verrouillé) */
input[readonly],
input:disabled {
  background: var(--readonly-bg);
  color: var(--muted);
  cursor: not-allowed;
  border-style: dashed;
}

/* ── Sélection « en langage naturel » : menus + texte alignés en une phrase ── */
.nl-form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;   /* aligne les menus par le bas, le titre du 1er flotte au-dessus */
  gap: 8px 10px;
}
.nl-group { display: flex; flex-direction: column; }   /* libellé « Type » au-dessus du 1er menu */
.nl-form select { width: auto; }                        /* largeur au contenu (pas 100%) */

/* Label « Attachement » : forcé en retour à la ligne (pleine largeur), au-dessus de
   la ligne des menus d'attachement — même style que le label « Type » (règle globale
   `label` : taille, graisse, margin-bottom). */
.nl-break-label {
  flex-basis: 100%;
  margin-top: 10px;
}

/* Label « Attachement » masqué (v1.57) : le 3e menu s'aligne sur la même ligne que Type +
   type d'embout (bannière compacte, le retour à la ligne venait de ce label en flex-basis:100%). */
#attachLabel { display: none !important; }

/* Élément masqué tant que l'étape précédente n'est pas validée */
.nl-hidden { display: none !important; }

/* L'attribut [hidden] doit l'emporter sur .field{display:flex} (sinon un champ masqué par JS —
   ex. P hors tuyauter — reste affiché). */
.field[hidden] { display: none !important; }

/* Mode 3 « attachement » retiré (v1.68 — un tenon ne casse jamais en pratique) : sa ligne-formule
   n'est plus pertinente → masquée (!important pour battre les règles d'affichage des formules). */
#formulaAttachementWrap { display: none !important; }

/* ── Légende du code couleur des champs (carte Sélection) ── */
.color-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 7px 8px;
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  font-size: 0.72rem;
  color: var(--muted);
}
.legend-item {
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 3px 10px;
}
.legend-dot {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  margin-right: 5px;
}
.legend-dot--manual { background: #1a1d21; }
.legend-dot--exact  { background: #4b5563; }   /* (v3.64/v3.75) gris auto assombri (retour utilisateur) */
.legend-dot--approx { background: #f97316; }
.legend-dot--error  { background: #dc2626; }

/* Filigrane (placeholder) d'un champ auto-rempli en cours d'édition :
   reprend la couleur de l'état auto (gris = exact, orange = palier supérieur) */
input.ph-exact::placeholder  { color: #4b5563; opacity: 1; }
input.ph-approx::placeholder { color: #f97316; opacity: 1; }

.grid {
  display: grid;
  grid-template-columns: 1fr;   /* une colonne : cartes étroites alignées à côté du schéma */
  gap: 14px;
}

/* Champ pleine largeur dans la grille (ex : Volume de l'encombrement) */
.field-full {
  grid-column: 1 / -1;
}

/* En-tête de section dans la carte (« Taille de l'attachement », « Cotes », « Manche ») :
   trait de séparation + label. (v3.54) Typo DIFFÉRENCIÉE des libellés de champs (retour
   utilisateur : « Manche » se confondait avec « Longueur ») : petites capitales espacées en
   bleu accent — cohérent avec la charte (repères accentués des titres de cartes). */
.section-divider {
  grid-column: 1 / -1;
  border-top: 1px solid var(--border);
  margin-top: 4px;
  padding-top: 16px;
  font-weight: 700;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--accent-strong);
}

/* Première section (juste sous le titre de la carte) : pas de double trait */
.section-divider--first {
  border-top: none;
  margin-top: 0;
  padding-top: 0;
}

/* (v4.53) En VUE CLIENT, la section « Taille de l'attachement » a été DÉPLACÉE dans la bannière
   Attachement (v4.46) → elle n'est plus dans la carte Dimensions. Le divider « Cotes » (#cotesDivider)
   est donc TOUJOURS la 1re section de la carte : son trait ferait doublon avec le soulignement du
   titre « Dimensions » → on le retire. (En ADMIN, #sectionAttachement est encore dans la carte —
   admin.html séparé — donc le trait « Cotes » reste utile : règle scopée view-client uniquement.) */
body.view-client #cardDimensions #cotesDivider {
  border-top: none;
  margin-top: 0;
  padding-top: 0;
}

/* ── Sections REPLIABLES de la carte « Dimensions » (Taille de l'attachement,
   Options supplémentaires) : <details>/<summary> natifs, chevron ▸ qui pivote.
   Le <summary> reprend le style .section-divider (trait + label). ── */
details.grid-section { grid-column: 1 / -1; }
details.grid-section > summary {
  cursor: pointer;
  list-style: none;          /* marqueur natif remplacé par le chevron ::before */
  user-select: none;
}
details.grid-section > summary::-webkit-details-marker { display: none; }
details.grid-section > summary::before {
  content: "▸";
  display: inline-block;
  margin-right: 7px;
  color: var(--muted);
  transition: transform 0.15s;
}
details.grid-section[open] > summary::before { transform: rotate(90deg); }
details.grid-section > .field { margin-top: 14px; }
details.grid-section > .field:last-child { margin-bottom: 0; }

/* Erreur bloquante (rouge pur) : valeur hors abaque.
   (v3.39) 0.78 → 0.85rem (revue dirigeants : messages sous champ plus lisibles). */
.field-error {
  color: #dc2626;
  font-size: 0.85rem;
  margin: 5px 0 0;
}

/* Avertissement non bloquant (orange) — ex. « Petite ouverture, valeurs suggérées surévaluées ».
   (v3.42, revue dirigeants) Aligné sur le style des NOTES de l'app (.dim-note : petit corps
   italique) plutôt que sur l'erreur pleine taille : plus discret et cohérent avec le reste. */
.field-warn {
  color: #f97316;
  font-size: 0.82rem;
  font-style: italic;
  font-weight: 500;
  line-height: 1.45;
  margin: 5px 0 0;
}

/* Message A/B vide : on le retire du flux (sinon les marges par défaut du <p>
   réservent ~32px sous le champ, éloignant l'alerte de proportions). */
.field > p:empty { display: none; }

/* Note d'imprécision de la cote B calculée (v0.87, remplace l'ancienne alerte
   orange « écart aux standards »). Ton NEUTRE (gris) : information, pas alerte —
   la formule B « au plus juste » a ±3–5 mm d'imprécision. */
.dim-note {
  color: var(--muted);
  font-size: 0.78rem;
  font-style: italic;
  margin: 0;                                 /* placée dans la grille : l'espacement vient du gap */
}
.dim-note:empty { display: none; }           /* masquée tant qu'il n'y a pas de message */

/* Note de provenance sous un champ auto-rempli : « (standard pour A=21) » */
.auto-note {
  display: block;
  color: var(--muted);
  font-size: 0.72rem;
  font-style: italic;
  margin: 4px 0 0;
}
.auto-note:empty { display: none; }

/* Formule ÉDITABLE (cotes B/S, encombrement, couples). Conteneur masqué via
   l'attribut [hidden] quand le champ ne s'applique pas au type courant.
   (v5.10) RESTYLE (demande utilisateur : « plus beaux, lisibles, professionnels ») :
   encart clair arrondi à liseré acier, libellé droit (fini l'italique), police mono
   plus grande et en couleur de texte pleine, focus visible. */
.formula-edit {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 6px 0 0;
  padding: 7px 10px;
  background: #f7f9fc;
  border: 1px solid var(--line);
  border-left: 3px solid var(--brand-steel);
  border-radius: 8px;
}
.formula-edit[hidden] { display: none; }
.formula-label {
  color: var(--brand-steel);
  font-size: 0.75rem;
  font-weight: 600;
  font-style: normal;
  letter-spacing: 0.01em;
  white-space: nowrap;
}
/* Résultat individuel d'une formule de couple (transparence des 3 modes de ruine) :
   affiché à droite du champ, masqué quand le mode n'est pas calculable (:empty).
   Le mode gouvernant est démarqué par .mode-governs (bloc couple). */
.formula-result {
  color: var(--text);
  font-size: 0.78rem;
  font-weight: 600;
  font-style: normal;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.formula-result:empty { display: none; }
/* Texte de règle NON éditable (ex. détail du calcul des poses) : même police que
   les formules, en lecture seule (simple texte, pas de champ). */
.formula-static {
  font-family: ui-monospace, "Cascadia Code", Consolas, monospace;
  font-size: 0.78rem;
  color: var(--text);
}
/* Champ de saisie de la formule : police mono, lisible, pleine largeur restante.
   Sélecteur `input.formula-input` : il faut ÉGALER la spécificité du style de base
   `input[type="text"]` (un attribut pèse comme une classe) pour le surcharger —
   `.formula-input` seul perdait silencieusement (champ rendu en 0.95rem). */
input.formula-input {
  flex: 1 1 auto;
  min-width: 0;
  font-family: ui-monospace, "Cascadia Code", Consolas, monospace;
  font-size: 0.8rem;
  color: var(--text);
  padding: 5px 8px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: #fff;
  transition: border-color 0.12s, box-shadow 0.12s;
}
input.formula-input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-soft);
  color: var(--text);
}
/* Formule invalide (erreur de syntaxe ou résultat non numérique) : bordure rouge. */
.formula-input.formula-input--invalid {
  border-color: #dc2626;
  color: #dc2626;
  background: #fff5f5;
}

/* Alerte RdM (rouge) affichée à côté du couple calculé quand la valeur saisie
   par l'utilisateur est inférieure au couple maximum supporté. */
.couple-warn {
  color: #dc2626;
  font-style: normal;
  font-weight: 600;
}

/* ── Résultats décisionnels mis en avant (badges) ──
   Le « Couple maximum supporté » et le « Coût matière brut » sont les deux sorties
   qui comptent : on les détache visuellement (fond bleuté). Les couleurs d'état
   restent prioritaires (alerte .couple-warn rouge ; coût en rouge inline si la
   section fournisseur est indisponible). */
#coupleMaxNote:not(:empty) {
  display: block;
  background: #eef4ff;
  border: 1px solid #c9dcff;
  border-radius: 6px;
  padding: 6px 10px;
  margin-top: 6px;
  color: #1e3a5f;
  font-size: 0.82rem;
  font-style: normal;
}
#coutMatiere,
input.price-badge {
  font-size: 1.05rem;
  font-weight: 600;
}
input.price-badge { font-size: 1.14rem; font-weight: 700; letter-spacing: -0.01em; }
input#coutMatiere[readonly],
input.price-badge[readonly] {
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  color: var(--accent-strong);
  cursor: default;
}
/* Prix UNITAIRE en retrait (bannière Devis du bas, v3.40 — revue dirigeants) : fond neutre,
   ton discret et corps réduit → seul le PRIX TOTAL garde le badge bleu accentué. */
input#prixUnitaireClient[readonly] {
  background: var(--surface-2);
  border-color: var(--border);
  color: var(--muted);
  font-size: 1rem;
  font-weight: 600;
}

/* ── Jauge « couple cible / capacité » — SOUS le champ Couple (carte Dimensions, v1.04)
   Compacte. Le nombre est déjà affiché par #coupleMaxNote ; la jauge n'ajoute que la
   barre + le statut. Posée par updateCoupleVerdict (app.js). Client uniquement
   (l'admin a le détail + les 3 modes sous le champ Couple) ; masquée à l'impression. */
.couple-verdict { margin: 6px 0 2px; }
body.view-admin .couple-verdict,
body.view-admin .devis-actions { display: none; }

/* Jauge : couple cible / capacité. Bleu = capacité (pas de cible), vert = couvert,
   rouge = dépassé. */
.hero-gauge {
  height: 6px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 999px;
  overflow: hidden;
}
.hero-gauge-fill {
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: var(--accent);
  transition: width 0.35s ease, background 0.2s;
}
.hero-gauge-fill.is-ok   { background: var(--ok); }
.hero-gauge-fill.is-over { background: var(--err); }
.hero-status {
  margin: 5px 0 0;
  font-size: 0.72rem;
  color: var(--muted);
}
.hero-status.is-ok   { color: var(--ok); font-weight: 600; }
.hero-status.is-over { color: var(--err); font-weight: 600; }

/* Action devis (imprimer / PDF) — sous la rangée de prix de la bannière. */
.devis-actions { margin: 12px 0 2px; }
/* (v4.31) « Valider mon outil et continuer » en bas du devis détaillé : bloc espacé, bouton
   pleine largeur pour un CTA clair de fin de page. Masqué en vue admin (client-only). */
body.view-client .devis-validate-bottom { margin-top: 18px; display: flex; }
body.view-client .devis-validate-bottom .hero-cta { width: 100%; }
/* Le conteneur se replie quand son bouton est masqué (panier-seul) → pas de marge fantôme. */
body.view-client .devis-validate-bottom:has(> button[hidden]) { display: none; }
body.view-admin .devis-validate-bottom { display: none; }
.hero-cta {
  width: auto;
  border: none;
  border-radius: var(--r-sm);
  background: var(--brand-gradient);   /* dégradé de marque (rappel du logomark) */
  color: #fff;
  font: inherit;
  font-weight: 650;
  font-size: 0.9rem;
  padding: 11px 18px;
  cursor: pointer;
  transition: filter 0.15s, box-shadow 0.15s;
  box-shadow: 0 2px 10px rgba(1,57,95,.30);   /* ombre marine de marque */
}
.hero-cta:hover  { filter: brightness(1.08); }   /* garde le dégradé */
.hero-cta:active { box-shadow: none; }
/* « Valider ma commande » (revue expert, v1.87) — ambre, distinct du devis bleu ; à côté du bouton grisé. */
.hero-cta--validate { background: linear-gradient(135deg, #f0a93a, #d97706); box-shadow: 0 2px 10px rgba(180,83,9,.30); margin-left: 10px; }
.hero-cta--validate[hidden] { display: none; }

/* Case à cocher « renouvellement » : libellé et case sur une ligne. */
.checkbox-field {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.85rem;
  font-weight: 500;
  cursor: pointer;
}
.checkbox-field input[type="checkbox"] {
  width: auto;
  margin: 0;
  cursor: pointer;
}

/* Détection « embout déjà réalisé » (v0.90) : note sous la case renouvellement.
   Ton informatif (vert = déjà réalisé, ambre = nouvel embout). Admin uniquement. */
.realise-note {
  display: block;
  font-size: 0.72rem;
  font-style: italic;
  margin: 4px 0 0;
}
.realise-note:empty { display: none; }
.realise-note--oui { color: #15803d; font-style: normal; font-weight: 600; }   /* vert : déjà réalisé */
.realise-note--std { color: var(--accent-strong); font-style: normal; font-weight: 600; }  /* bleu : standard fabricant */
.realise-note--non { color: #b45309; }                                          /* ambre : nouvelle pièce */
.realise-note--val { color: #9a6700; font-style: normal; font-weight: 600; }    /* ocre : plan en validation */
/* Plan pour validation (catalogue) : badge sur la réf (compact) + case (v1.73). */
.cat-val-badge { display: inline-block; font-size: 0.64rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; color: #9a6700; background: #fdf3d8; border: 1px solid #ecd9a0; border-radius: 4px; padding: 1px 5px; vertical-align: middle; }
/* Pastille d'options (α/β/γ/E ≠ 0) sur la ligne de réf du catalogue (v2.08). */
.cat-opt-badge { display: inline-block; font-size: 0.64rem; font-weight: 700; color: var(--accent-strong); background: var(--accent-soft); border: 1px solid var(--accent-line); border-radius: 4px; padding: 1px 6px; vertical-align: middle; margin-left: 4px; }
.cat-detail-val { flex-direction: row !important; align-items: center; gap: 6px; }
.cat-detail-val input { width: auto; }
.cat-val-cell { text-align: center; }

/* Bouton « Marquer comme réalisé » (ajoute l'embout à la base locale). */
.btn-marquer {
  margin-top: 6px;
  font-size: 0.72rem;
  padding: 3px 10px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: #fff;
  cursor: pointer;
  width: auto;
}
.btn-marquer:hover { border-color: #aeb9c4; }
.btn-marquer[hidden] { display: none; }

/* ── Plan client de la pièce déjà réalisée (PNG) — v1.54 ── */
.schema-col--plan { height: 340px; padding: 38px 14px 14px; }
.schema-col--plan::after { content: none; }              /* pas de chip légende : le bandeau-titre porte la réf */
.schema-col--plan[hidden] { display: none; }
#planRealiseImg { max-width: 100%; max-height: 100%; object-fit: contain; display: block; }
body.view-client .schema-col--plan { height: 300px; }

/* Upload du plan (admin), à côté de « Marquer comme réalisé » */
.plan-upload { display: flex; align-items: center; gap: 8px; margin-top: 6px; flex-wrap: wrap; }
.plan-upload[hidden] { display: none; }
.btn-plan { font-size: 0.72rem; padding: 3px 10px; border: 1px solid var(--border); border-radius: 6px; background: #fff; cursor: pointer; width: auto; }
.btn-plan:hover { border-color: #aeb9c4; }
.plan-upload-note { color: var(--muted); font-size: 0.72rem; }

/* Colonne « plan » de la table catalogue (admin) */
.cat-plan-cell { white-space: nowrap; }
.cat-thumb { height: 30px; width: auto; vertical-align: middle; border: 1px solid var(--line); border-radius: 3px; margin-right: 6px; background: #fff; cursor: zoom-in; transition: box-shadow .12s, border-color .12s; }
.cat-thumb:hover { border-color: var(--brand-steel, #83a4cb); box-shadow: 0 0 0 2px rgba(131,164,203,.35); }
/* Plan en PLEIN ÉCRAN (clic sur la miniature, v2.40) : image centrée, sans cadre. */
.cat-thumb:fullscreen { height: auto; width: auto; max-width: 96vw; max-height: 94vh; border: 0; border-radius: 0; cursor: zoom-out; margin: 0; }
.cat-plan { font: 600 0.64rem/1 inherit; text-transform: uppercase; letter-spacing: .06em; padding: 3px 9px; border: 1px solid var(--border); border-radius: 6px; background: var(--surface); color: var(--brand-navy, #01395f); cursor: pointer; transition: border-color .12s, background .12s; }
.cat-plan:hover { border-color: var(--brand-steel, #83a4cb); background: #f3f7fb; }
/* « Changer » / « + Plan » : visible seulement quand la LIGNE est en édition (vue compacte) — l'admin
   a cliqué la flèche à côté de la réf. En vue détaillée (pas de .cat-detail), il reste visible. */
.cat-detail:not(.cat-editing) .cat-plan { display: none; }

/* ── Prix dégressif (vue client) : prix unitaire 1 / 3 / 5 pièces — v1.55 ── */
.devis-degressif:empty { display: none; }
.devis-degressif { margin-top: 12px; }
.deg-title { font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; color: var(--muted); margin-bottom: 8px; }
.deg-row { display: flex; gap: 10px; flex-wrap: wrap; }
.deg-cell { flex: 1 1 120px; display: flex; flex-direction: column; align-items: center; gap: 1px; padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface-2); cursor: pointer; transition: border-color 0.15s, box-shadow 0.15s; }
.deg-cell:hover { border-color: var(--accent); }
/* Encadré ACTIF (v3.13) : SEULE la cellule dont n == quantité saisie est surlignée
   (1 pièce par défaut) — remplace l'ancien surlignage fixe de la cellule « 5 pièces ». */
.deg-cell.is-active { border-color: var(--accent-strong); box-shadow: 0 0 0 1px var(--accent-strong); }
.deg-q { font-size: 0.72rem; color: var(--muted); }
.deg-pu { font-size: 1.05rem; font-weight: 700; color: var(--text); }
.deg-u { font-size: 0.64rem; color: var(--muted); }
.deg-tot { font-size: 0.64rem; color: var(--muted); margin-top: 3px; }

/* ── Éditeur Gamme & taux (admin) — v1.55 ── */
/* (v3.76) GAMME & TAUX au style « RÉGLAGES » des Variables (v3.72-74) — étendu sur demande
   utilisateur. CSS seul : le balisage généré (ui-gamme.js) et les écouteurs sont inchangés. */
.gamme-h3 { font-size: 0.78rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.07em;
  margin: 20px 0 4px; color: var(--accent-strong); }   /* même typo que les .section-divider */
.gamme-h3:first-child { margin-top: 0; }
.gamme-note { font-size: 0.72rem; color: var(--muted); margin: 0 0 8px; }
/* Taux horaires : 2 colonnes de LIGNES « libellé · champ aligné à droite » (comme Variables). */
.gamme-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 40px; margin-bottom: 6px; }
.gamme-field { display: grid; grid-template-columns: minmax(0, 1fr) 130px; column-gap: 16px;
  align-items: center; padding: 8px 2px; border-bottom: 1px solid var(--line);
  font-size: 0.85rem; color: var(--text); }
.gamme-field > span { font-weight: 500; }
#cardGamme .gamme-field input, #cardGamme .gamme-poste { width: 110px; min-width: 90px; }
#cardGamme .gamme-field input { width: 130px; justify-self: end; text-align: right;
  font-variant-numeric: tabular-nums; font-weight: 600; font-size: 0.95rem; }
/* La FORMULE (jet d'eau) reste pleine largeur, alignée à gauche (c'est une expression, pas un nombre). */
#cardGamme .gamme-field--wide { display: block; max-width: none; padding: 10px 2px 2px; border-bottom: none; }
#cardGamme .gamme-field--wide input { width: 100%; text-align: left; font-weight: 400; font-size: 0.9rem; }
@media (max-width: 760px) {
  .gamme-grid { grid-template-columns: minmax(0, 1fr); }
}
/* Champ FORMULE (jet d'eau) pleine largeur, police mono (v1.79). */
.gamme-field--wide { display: block; margin: 6px 0 4px; max-width: 520px; }
#cardGamme .gamme-field--wide input { width: 100%; box-sizing: border-box; }
.gamme-table { margin-top: 4px; }
.gamme-dyn { font-size: 0.64rem; color: var(--accent-strong); border: 1px solid var(--accent-strong); border-radius: 999px; padding: 0 6px; margin-left: 4px; vertical-align: middle; }
.gamme-readonly { color: var(--muted); font-size: 0.78rem; }

/* ── Sélecteur de type en TUILES (vue client, état « aucun type choisi ») — v1.56 ── */
.type-picker { display: none; position: relative; }
/* Un seul sélecteur à la fois (ID, pas la classe partagée) : type tant qu'aucun type, puis
   attachement tant qu'aucun attachement. La zone de config normale est masquée pendant ce temps. */
/* Étape « catégorie » (v2.18) : tuiles Embouts / Clés, AVANT le choix du type. */
body.view-client.client-no-cat    #categoryPicker { display: block; }
body.view-client.client-no-type   #typePicker   { display: block; }
/* v3.21 : les étapes en tuiles « liaison » et « attachement » des embouts sont REMPLACÉES par la
   bannière de configuration (#clientConfigBar, 2 menus déroulants) — moins de clics.
   v3.47 : IDEM pour la DOUILLE — l'étape en tuiles « carré » (v3.08) disparaît, le carré se
   configure dans la même bannière (menus proxys de #douilleCarre / #douilleCarrePerso). */
/* Douille : en client, le carré (v3.47) ET l'empreinte (v3.96) se configurent dans la
   BANNIÈRE « Attachement & Empreinte » → on masque les champs du form (conservés pour
   l'admin), ainsi que le divider « Entraînement & empreinte » (son trait faisait une barre
   sous le titre « Dimensions ») : la carte démarre directement sur A. */
body.view-client #fieldDouilleCarre,
body.view-client #fieldDouilleCarrePerso,
body.view-client #fieldDouilleEmpreinte,
body.view-client #douilleEntrainementDivider { display: none; }
body.view-client.client-no-cat    .selection-row,
body.view-client.client-no-cat    .layout,
body.view-client.client-no-type   .selection-row,
body.view-client.client-no-type   .layout { display: none; }
/* La bannière de configuration (et la rangée « Changer de type ») suit le même masquage que
   la .layout pendant les étapes tuiles. */
body.view-client.client-no-cat  #clientConfigBar,
body.view-client.client-no-cat  #configBackRow,
body.view-client.client-no-type #clientConfigBar,
body.view-client.client-no-type #configBackRow { display: none; }

/* ── FUNNEL EN TUILES ACCUMULANTES (vue client, EMBOUTS — v4.37/v4.38) ────────────────────
   N'assure que TÊTE → ATTACHEMENT (2 étapes en tuiles qui s'accumulent : la tuile choisie reste
   ENCADRÉE .is-selected, ses sœurs GRISÉES .is-dimmed ; rangée franchie .is-locked, active .is-open).
   Une fois l'attachement choisi, la classe body.client-embout-tiles tombe → on retrouve la page de
   configuration classique (bannière #clientConfigBar). Couche PROXY (#typeEmbout / #liaison). */
/* padding-bottom: 50vh → il y a TOUJOURS de quoi défiler sous la dernière rangée, pour que
   l'étape active puisse être ramenée au CENTRE de la fenêtre même quand la page est courte (sinon
   le scroll bute en bas et l'étape reste collée en bas au lieu d'être centrée — v4.44). */
.tile-funnel { display: none; max-width: 1080px; margin: 0 auto; position: relative; padding-bottom: 50vh; }
body.view-client.client-embout-tiles #emboutFunnel { display: block; }
/* Pendant la phase TUILES (tête/attachement non encore choisis), on masque TOUT sauf le funnel :
   l'écran de type, la page de configuration (bannière + layout + rangée « Sélection ») ET la
   BANNIÈRE DE DEVIS du bas (#cardDevis, hors .layout) — pour ne montrer QUE les tuiles (v4.40). */
body.view-client.client-embout-tiles #typePicker,
body.view-client.client-embout-tiles .layout,
body.view-client.client-embout-tiles .selection-row,
body.view-client.client-embout-tiles .selection-summary,
body.view-client.client-embout-tiles #clientConfigBar,
body.view-client.client-embout-tiles #configBackRow,
body.view-client.client-embout-tiles #cardDevis { display: none !important; }

.tile-funnel .picker-back { position: static; padding-left: 0; margin: 4px 0 12px; }

.funnel-row { margin: 0 0 8px; padding: 16px 0 6px; border-top: 1px solid var(--border); }
.funnel-row:first-of-type { border-top: 0; padding-top: 4px; }
.funnel-row[hidden] { display: none; }
.funnel-row-title { font-size: 1.2rem; font-weight: 700; color: var(--text); text-align: center; margin: 0 0 16px; }
/* (v4.45) FONDU d'apparition de l'étape ACTIVE : la rangée qui se déplie (ex. les tuiles
   d'attachement après le choix de la tête) entre en douceur au lieu de surgir. Ne rejoue qu'au
   passage à `is-open` (classe stable ensuite → une seule fois). */
@keyframes funnelStepEnter { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
.funnel-row.is-open { animation: funnelStepEnter .3s ease both; }

/* (v4.45) FONDU d'apparition de la PAGE DE CONFIGURATION à la sortie des tuiles (choix de
   l'attachement) — masque le remplacement instantané display:none↔block. Classe transitoire
   `embout-enter-config` posée par app.js le temps de l'animation. */
@keyframes emboutConfigEnter { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
body.view-client.embout-enter-config #clientConfigBar,
body.view-client.embout-enter-config .layout,
body.view-client.embout-enter-config #cardDevis { animation: emboutConfigEnter .34s ease both; }

/* Rangée FRANCHIE (verrouillée) : les tuiles gardent leur FORMAT PLEIN (demande utilisateur) — la
   tuile retenue reste encadrée, ses sœurs grisées MAIS CLIQUABLES (cliquer une tuile grisée re-choisit
   cette valeur et revient juste après son étape). L'étape suivante apparaît en dessous (scroll doux). */
.funnel-row.is-locked .funnel-row-title { color: var(--muted); font-size: 1rem; margin-bottom: 10px; }
.funnel-row.is-locked .type-tile.is-dimmed { opacity: .45; filter: grayscale(.6); cursor: pointer; }
.funnel-row.is-locked .type-tile.is-dimmed:hover { opacity: .85; filter: grayscale(.1);
  transform: translateY(-3px); border-color: var(--accent-strong); }
.type-tile.is-selected { border-color: var(--accent-strong);
  box-shadow: 0 0 0 2px var(--accent-strong) inset, var(--shadow-sm); background: var(--surface); }
.type-tile.is-selected:hover { transform: none; }   /* la tuile encadrée ne « saute » pas au survol */

/* (v4.46) Dimensions CLASSIQUES : l'image du schéma d'attachement (forme, à gauche) reste MASQUÉE
   tant que « Taille de l'attachement » n'est pas ouverte (body.attach-size-open) — embouts seulement
   (clé/douille masquent déjà cette boîte via .is-cle/.is-douille). */
body.view-client:not(.attach-size-open) .schema-stack:not(.is-cle):not(.is-douille) .schema-col--attach { display: none; }
/* (v4.56) Boîte du schéma de forme SANS image (ex. « Personnalisées » avant choix de forme) →
   masquée, pour ne pas laisser un cadre vide en arrière-plan de la modale de choix. */
.schema-col--attach.attach-schema-empty { display: none; }

/* (v4.39) FORME de l'attachement PERSONNALISÉ — les tuiles s'affichent EN MODALE (par-dessus la
   page, patron .help-modal) ; carte élargie pour la grille de tuiles. */
/* ⚠️ double classe (.help-card.forme-overlay-card) pour battre `.help-card { max-width:380px }`
   définie PLUS BAS — sinon la modale reste étroite et les tuiles s'empilent (v4.43). */
.help-card.forme-overlay-card { max-width: 760px; width: 92%; text-align: left; }
/* Tuiles de forme EN LIGNE HORIZONTALE (flex + retour à la ligne) — plusieurs lignes quand les
   images seront ajoutées (v4.41). Sans image : tuiles compactes pour tenir sur une ligne.
   ⚠️ id (#formeOverlayTiles) pour battre `.type-picker-grid { display:grid }` (définie plus bas). */
#formeOverlayTiles.forme-overlay-tiles { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; margin-top: 14px; }
.forme-overlay-tiles .type-tile { flex: 0 1 auto; width: auto; min-width: 128px; padding: 14px 16px; gap: 8px; }
.forme-overlay-tiles .type-tile.type-tile--custom { min-height: 0; justify-content: center; }
.forme-overlay-tiles .type-tile img { height: 96px; }   /* image (à venir) → tuiles plus hautes → wrap sur plusieurs lignes */
.forme-overlay-tiles .type-tile-name { font-size: 1rem; }

/* (v4.39) FORME RETENUE, affichée à GAUCHE au-dessus du schéma technique — cliquable pour rouvrir
   la modale. */
.forme-selected { cursor: pointer; margin-bottom: 12px; padding: 12px; border: 1px solid var(--border);
  border-radius: var(--r); background: var(--surface); box-shadow: var(--shadow-sm);
  transition: border-color .12s ease, box-shadow .12s ease; }
.forme-selected:hover { border-color: var(--accent-strong); box-shadow: 0 6px 16px rgba(0,0,0,0.08); }
.forme-selected .forme-selected-label { display: block; font-size: 0.72rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin: 0 0 8px; }
.forme-selected .forme-selected-name { font-size: 1.05rem; font-weight: 700; color: var(--text); }
.forme-selected .forme-selected-hint { display: block; margin-top: 6px; font-size: 0.8rem; color: var(--accent-strong); }
.forme-selected img { width: 100%; max-width: 160px; height: 90px; object-fit: contain; display: block; margin: 0 auto 6px; }

/* ── BANNIÈRE DE CONFIGURATION (vue client, embouts — v3.21) : 2 menus déroulants (liaison +
   attachement, + forme en sur-mesure). v3.28 : les cartes sont désormais VISIBLES dès le type
   choisi, champs GRISÉS tant que la bannière n'est pas complète. v3.35 : titre « Attachement »,
   « ← Changer de type » déplacé AU-DESSUS (rangée dédiée, TOUS les types d'outils). ── */
.config-back-row { margin-bottom: 6px; }
.config-back-row[hidden] { display: none; }
.config-back-row .picker-back { position: static; padding-left: 0; }
.client-config-bar {
  background: var(--card); border: 1px solid var(--border); border-radius: var(--r);
  box-shadow: var(--shadow-sm); padding: 14px 18px; margin-bottom: 18px;
  display: flex; align-items: flex-end; gap: 18px; flex-wrap: wrap; position: relative;
}
.client-config-bar[hidden] { display: none; }
/* Titre de la bannière (v3.35 ; v4.57 : même BANDEAU + barre latérale accent que les titres de
   cartes « Dimensions »/« Devis » — cf. .card > h2). Marges négatives = padding de la bannière
   (14px 18px) pour que le bandeau colle aux bords. */
.ccb-title {
  display: flex; align-items: center; gap: 9px; flex-basis: 100%;
  font-size: 1.05rem; font-weight: 700; letter-spacing: 0.01em; color: var(--brand-navy);
  /* (v4.60) Marge basse à 0 : la séparation avec le 1er champ vient du `gap: 18px` de la
     bannière (comme les cartes : titre → corps = 18 px). Évite le double-espace (18 + 16 = 34 px)
     qui déséquilibrait le bandeau. */
  margin: -14px -18px 0; padding: 12px 18px 11px;
  background: var(--surface-2); border-bottom: 1px solid var(--border);
  border-radius: calc(var(--r) - 1px) calc(var(--r) - 1px) 0 0;
}
.ccb-title::before { content: ""; flex: none; width: 4px; height: 15px; border-radius: 2px; background: var(--brand-gradient); }
/* Sous-titre EXPLICATIF de la bannière (v3.51, wording client) — posé par syncConfigBar selon
   la catégorie ; vide = masqué. */
.ccb-sub { flex-basis: 100%; margin: -6px 0 0; font-size: 0.78rem; color: var(--muted); }
.ccb-sub:empty { display: none; }
.ccb-field { display: flex; flex-direction: column; gap: 4px; min-width: 220px; }
.ccb-field[hidden] { display: none; }   /* [hidden] doit BATTRE le display:flex de la classe (v3.28) */
.ccb-field label { font-size: 0.78rem; font-weight: 600; color: var(--muted); }
.ccb-field select { font: inherit; padding: 9px 10px; border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface); }
/* Champ TEXTE de la bannière (taille du carré perso des douilles, v3.47) — même habillage que
   les menus ; le repère « = XX mm » s'affiche dessous en petit. */
.ccb-field input[type="text"] { font: inherit; padding: 9px 10px; border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface); }
.ccb-input-hint { font-size: 0.78rem; color: var(--muted); min-height: 1em; }
/* CONTOUR SURLIGNÉ (v3.36) : posé UNIQUEMENT après un clic sur un champ grisé ou sur la
   mention « Veuillez configurer l'attachement » (classe ccb-alert, openFirstConfigMenu) — plus
   de surlignage « par défaut » quand la bannière est simplement incomplète (ccb-incomplete ne
   garde que le rôle d'état pour l'invite). Retiré dès que la bannière est remplie (syncConfigBar).
   (v3.59) AMBRE assorti à la pastille (le bleu v3.58 se fondait dans la charte — le halo doit
   se voir au moment du clic) ; ton doux, pas l'orange plein v3.36. */
.client-config-bar.ccb-alert { border-color: #e8a33d; box-shadow: 0 0 0 2px rgba(232, 163, 61, 0.30); }
.ccb-hint { flex-basis: 100%; margin: 0; font-size: 0.82rem; color: var(--accent-strong); }
.ccb-hint[hidden] { display: none; }
/* (v4.48) « Taille de l'attachement » : sur sa PROPRE ligne, JUSTE SOUS le menu de dimensions
   (la bannière est en flex-wrap → sans flex-basis:100% la section se plaçait À CÔTÉ du menu). */
.client-config-bar #sectionAttachement { flex-basis: 100%; margin: 0; }   /* (v4.60) rythme uniforme : séparation = gap 18px de la bannière */
.client-config-bar #sectionAttachement > .field { max-width: 220px; }
/* (v4.53) Le TRAIT séparateur, « déplacé » avec la section : il vit désormais dans la bannière,
   au-dessus de « Taille de l'attachement » (pour la séparer du menu de dimensions) — et non plus
   au-dessus de « Cotes » dans la carte Dimensions. */
.client-config-bar #sectionAttachement > summary { border-top: 1px solid var(--border);
  margin-top: 0; padding-top: 14px; }   /* (v4.60) trait : 18px au-dessus (gap) / 14px en dessous — équilibré */

/* ── ATTACHEMENT NON CONFIGURÉ (v3.28) : les cartes sont visibles dès le type choisi, mais les
   CHAMPS restent grisés/non saisissables tant que la bannière n'est pas complète, et la mention
   « Veuillez configurer l'attachement » s'affiche à côté du titre « Dimensions ».
   (v3.33) Mention CLIQUABLE — comme les zones grisées, elle remonte à la bannière et déroule
   le premier menu à remplir (openFirstConfigMenu).
   (v3.58→v3.59) Deux excès corrigés : l'orange PLEIN v3.33 était criant, mais le bleu charte
   v3.58 se FONDAIT dans le reste (retour utilisateur : « pas assez visible »). → AMBRE DOUX :
   fond clair ambré + texte ocre foncé + bord ambre — seule tache chaude de la page (tout le
   reste est bleu), elle se repère au premier regard sans crier ; au survol elle s'affirme
   (fond ambre, texte blanc). */
.dim-config-hint { display: none; margin-left: 12px; font-size: 0.82rem; font-weight: 600;
  color: #92510a; background: #fdf1de; border: 1px solid #eebc6d;
  border-radius: 999px; padding: 3px 12px; vertical-align: middle; cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s; }
.dim-config-hint:hover { background: #e8a33d; border-color: #e8a33d; color: #fff; }
body.view-client.attach-pending #dimConfigHint { display: inline-block; }
body.view-client.attach-pending #cardDimensions input,
body.view-client.attach-pending #cardDimensions select,
body.view-client.attach-pending #cardOptions input,
body.view-client.attach-pending #cardOptions select,
body.view-client.attach-pending #cardDevisCompact button:not(#btnHelpDevis),
body.view-client.attach-pending #cardDevis input {
  pointer-events: none; opacity: 0.55; background: var(--surface-2);
}
/* Le clic sur une zone grisée est INTERCEPTÉ par la carte (pointer-events:none sur les champs) →
   curseur main : « cliquer ici vous ramène à la configuration de l'attachement ». */
body.view-client.attach-pending #cardDimensions .grid,
body.view-client.attach-pending #cardOptions,
body.view-client.attach-pending #cardDevisCompact { cursor: pointer; }
/* Tuile « sur-mesure » (sans image) + lien retour vers le choix du type */
.type-tile--custom { justify-content: center; min-height: 152px; border-style: dashed; }
/* « ← Changer de … » (v3.28, revue dirigeants) : PLUS GROS et PLUS VISIBLES — police du site
   (Inter héritée : les <button> ne l'héritent pas seuls), flèche agrandie, pilule au survol.
   Commun à tous les pickers (catégorie / type / carré douille) et à la bannière de config. */
.picker-back {
  position: absolute; left: 0; top: 4px;
  display: inline-flex; align-items: baseline; gap: 2px;
  background: none; border: none; cursor: pointer;
  font-family: inherit; font-size: 1.05rem; font-weight: 650; color: var(--accent-strong);
  padding: 8px 14px; border-radius: 999px; transition: background .15s;
}
.picker-back::first-letter { font-size: 1.55em; }   /* la flèche « ← » nettement plus grande (v3.61 : 1.25 → 1.55) */
.picker-back:hover { background: var(--accent-soft); }
/* (v3.78) Écran ÉTROIT (téléphone / fenêtre non plein écran) : positionné en ABSOLU à gauche,
   le bouton retour CHEVAUCHAIT le titre centré (constaté à 375 et ~709 px — retour utilisateur).
   Sous 900 px il repasse dans le FLUX, seul sur sa ligne au-dessus du titre. */
@media (max-width: 900px) {
  .picker-back { position: static; display: inline-flex; margin: 2px 0 0; }
  .type-picker-title { margin-top: 8px; }
}

/* ── Vue client (v1.58) : le bandeau Sélection est REMPLACÉ par le schéma. Le titre du schéma
   devient l'en-tête de la pièce (désignation + bouton « Annuler »). Le choix de la FORME en
   sur-mesure passe par la bannière de configuration (v3.21) — plus d'exception sel-perso. ── */
body.view-client #cardSelection { display: none; }
body.view-client .schema-title {
  font-size: 0.95rem; font-weight: 700;
  padding: 8px 44px 8px 14px;   /* (v4.64) place réservée à droite pour le bouton plein écran (coin haut-droit) */
  background: rgba(255,255,255,0.97);
}
/* Gros bouton « Resélectionner » (vue client) : pleine largeur, au-dessus du schéma → rouvre
   les tuiles de type pour reconfigurer une autre pièce. */
.btn-reselect {
  width: 100%;
  padding: 11px 14px;
  font-size: 0.9rem; font-weight: 700;
  color: var(--accent-strong);
  background: var(--surface);
  border: 1px solid var(--accent-strong);
  border-radius: var(--r-sm);
  cursor: pointer;
}
.btn-reselect:hover { background: var(--surface-2); }

/* ── Clés & douilles : pas d'attachement → on masque la boîte du schéma d'attachement. Le schéma
   TECHNIQUE (#schemaTechBox) reste visible et porte le TEMPLATE paramétrable (clé v2.57, douille v2.89). ── */
.schema-stack.is-cle .schema-col--attach,
.schema-stack.is-douille .schema-col--attach { display: none; }

/* ── DOUILLE (v2.89, Phase 3) — la catégorie « Douille » remplace attachement + cotes embout par
   #sectionDouille (carré + empreinte). Masquage COSMÉTIQUE (CSS) des éléments propres à l'embout ;
   le vrai cloisonnement (calcul couple/cotes) est piloté par isDouille() dans app.js. ── */
body.cat-douille #cotesDivider,
body.cat-douille #fieldA, body.cat-douille #fieldB, body.cat-douille #fieldH, body.cat-douille #fieldS,
body.cat-douille .dim-colhead,
body.cat-douille #optionsSuppWrap,
body.cat-douille #feasBadge,
body.cat-douille #clientEmptyHint,
body.cat-douille #maneuverNote { display: none !important; }
/* Template douille : image PAYSAGE (ratio ~1,9) → elle remplit toute la LARGEUR du cadre. v2.92. */
body.view-client .schema-stack.is-douille #imageSchema,
body.view-admin  .schema-stack.is-douille #imageSchema { max-width: 100%; max-height: 100%; }
/* …et la BOÎTE technique épouse l'image au lieu de s'étirer (flex) sur toute la colonne des cotes,
   ce qui noyait le schéma paysage dans un grand cadre vide. Elle hugge donc son contenu. v2.92. */
body.view-client .schema-stack.is-douille #schemaTechBox,
body.view-admin  .schema-stack.is-douille #schemaTechBox { flex: 0 0 auto; min-height: 0; height: auto; }

/* ── Cotes douille (v2.96) : affichage PROGRESSIF + « Personnaliser les cotes » ──
   - Rien tant que carré + A ne sont pas renseignés (body.douille-ready absent).
   - En CLIENT hors « Personnaliser » : cotes autocomplétées montrées en RÉSUMÉ lecture seule ;
     les champs éditables (et le séparateur) n'apparaissent qu'en admin ou si « Personnaliser » coché. */
body:not(.douille-ready) .douille-ready-only,
body:not(.douille-ready) .douille-cote-edit { display: none !important; }
body.view-client:not(.douille-perso-on) .douille-cote-edit { display: none !important; }
body.view-admin #douilleCotesSummary,
body.douille-perso-on #douilleCotesSummary { display: none !important; }
/* Cotes autocomplétées en PASTILLES (v2.99) — mêmes .sv-chip que les suggestions embout/clé. */
.douille-cotes-summary { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0 0; }
/* « Douille à choc » sur sa propre ligne (v2.99/v3.10 ; la case « Personnaliser les cotes » a
   été retirée en v3.22 — cotes directement éditables). */
#fieldDouilleChocs { display: flex; }   /* ID : prime sur .checkbox-inline (inline-flex) */

/* ── LISIBILITÉ CLIENT (v3.49) — les lettres de cotes (A, D1…L2) ne parlent pas à un novice ──
   Sous-libellé PARLANT sous chaque champ douille (vue client seulement : l'admin connaît les
   lettres et privilégie la densité). */
.field-help { display: block; margin-top: 3px; font-size: 0.72rem; line-height: 1.3; color: var(--muted); }
.field-help--inline { display: inline; margin: 0 0 0 4px; }
.field-help:empty { display: none; }   /* aides pilotées par famille (v3.50, updateCoteHelps) : vide = masquée */
body.view-admin .field-help { display: none; }
/* Les aides longues ne doivent PAS élargir la grille (mobile 375 px) : un item de grille ne
   rétrécit pas sous son min-content par défaut → on l'y autorise, le texte passe à la ligne.
   (v3.50 : même protection pour la grille des cotes embouts/clés, qui reçoit les aides A/B/H/S.)
   (v3.53 : étendu aux .checkbox-inline de la grille — « Ajuster les cotes » et son aide.) */
#sectionDouille > .field, #sectionDouille > .checkbox-inline,
#cardDimensions .grid > .field,
#cardDimensions .grid > .checkbox-inline { min-width: 0; }
/* Cases « Ajuster les cotes » (v3.53) : libellé + aide repliables (checkbox-inline est nowrap
   par défaut) — l'aide passe SOUS la case, indentée, comme la case « à choc ». */
#ajusterCotesWrap, #ajusterCotesDouilleWrap { display: flex; flex-wrap: wrap; white-space: normal; }
#ajusterCotesWrap .field-help--inline,
#ajusterCotesDouilleWrap .field-help--inline { flex-basis: 100%; margin: 2px 0 0 24px; }
/* Case « à choc » : l'aide « (visseuse à percussion) » reste À CÔTÉ de la case (v3.95,
   retour utilisateur — elle passait dessous) ; le wrap ne sert que d'issue sur écran étroit. */
#fieldDouilleChocs { flex-wrap: wrap; }
#fieldDouilleChocs .field-help--inline { flex-basis: auto; margin: 0 0 0 4px; align-self: center; }

/* Légende CLIQUABLE du schéma douille (v3.49) : lettres → mots simples ; cliquer une ligne
   amène au champ (focus + surbrillance .field-flash). Vue client. (v4.01) MÊME légende pour
   les EMBOUTS et les CLÉS : #schemaCotesLegend (peuplée par type par updateSchemaLegend,
   affichée dès qu'elle a du contenu) partage la classe et les styles. */
.douille-legend { display: none; }
body.view-client.cat-douille #douilleSchemaLegend,
body.view-client #schemaCotesLegend:not(:empty) {
  display: grid; grid-template-columns: 1fr 1fr; gap: 2px 10px;
  margin: 10px 12px 6px; padding: 8px 10px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r-sm);
  /* #schemaTechBox est un FLEX ROW : sans ceci la légende se place À CÔTÉ de l'image
     (débordement mobile) — pleine largeur → le flex la replie SOUS le schéma. */
  flex-basis: 100%; min-width: 0;
}
body.view-client #schemaTechBox { flex-wrap: wrap; }
.douille-legend button {
  display: block; text-align: left; font: inherit; font-size: 0.72rem; color: var(--muted);
  background: none; border: 0; padding: 2px 4px; border-radius: 4px; cursor: pointer;
}
.douille-legend button b { color: var(--text); font-variant-numeric: tabular-nums; margin-right: 2px; }
.douille-legend button:hover { background: var(--accent-soft); color: var(--text); }
.douille-legend button[hidden] { display: none; }   /* lignes D3/E masquées (tournevis) */

/* ── HOTSPOTS CLIQUABLES sur le schéma (v3.51, embouts + douilles, vue client) : zones posées
   SUR les lettres du template (SCHEMA_HOTSPOTS, coordonnées % par image). Invisibles au repos
   (le dessin reste propre), cercle accent au survol/focus ; l'astuce sous le schéma assure la
   découvrabilité. L'overlay est positionné sur l'IMAGE par updateSchemaHotspots. ── */
#schemaTechBox { position: relative; }   /* ancre de l'overlay (client ; l'alignement admin pose son inline absolute) */
.schema-hotspots { position: absolute; display: none; z-index: 2; pointer-events: none; }
.schema-hotspot {
  position: absolute; width: 38px; height: 38px; transform: translate(-50%, -50%);
  border-radius: 50%; border: 0; padding: 0; background: transparent; cursor: pointer;
  pointer-events: auto;
}
.schema-hotspot:hover, .schema-hotspot:focus-visible {
  background: rgba(1, 57, 95, 0.10); box-shadow: 0 0 0 2px var(--accent-line);
}
/* (v4.02) La mention vit AU-DESSUS de la légende (ordre DOM), en UNE ligne horizontale —
   le retour à la ligne n'est autorisé que sur écran étroit (téléphone / fenêtre réduite). */
.schema-hotspot-hint { flex-basis: 100%; margin: 2px 12px 4px; font-size: 0.72rem; color: var(--muted); text-align: center; white-space: nowrap; }
@media (max-width: 900px) { .schema-hotspot-hint { white-space: normal; } }
.schema-hotspot-hint[hidden] { display: none; }
body.view-admin .schema-hotspot-hint { display: none; }

/* Surbrillance temporaire du champ ciblé par la légende (posée/retirée par app.js). */
.field-flash { animation: fieldFlash 1.6s ease-out; border-radius: var(--r-sm); }
@keyframes fieldFlash {
  0%   { box-shadow: 0 0 0 3px rgba(1, 57, 95, 0.45); background: var(--accent-soft); }
  100% { box-shadow: 0 0 0 3px rgba(1, 57, 95, 0);   background: transparent; }
}

/* Couple admissible de la douille (v2.91, inc. 2) — ligne de résultat mise en avant (bandeau
   accent quand un carré est choisi ; sinon invite discrète). */
.douille-couple { margin: 6px 0 0; font-size: 0.9rem; color: var(--muted); }
.douille-couple--ok { color: var(--text); background: var(--accent-soft); border: 1px solid var(--accent-line); border-radius: var(--r-sm); padding: 10px 12px; }
.douille-couple--ok strong { color: var(--accent-strong); font-size: 1.05rem; font-variant-numeric: tabular-nums; }
.douille-couple-src { display: block; margin-top: 3px; font-size: 0.78rem; color: var(--muted); font-weight: 400; }
.douille-couple-alt { display: block; margin-top: 3px; font-size: 0.72rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.douille-couple-caveat { display: block; margin-top: 5px; font-size: 0.78rem; color: var(--warn); font-weight: 600; }

/* ── Bouton PLEIN ÉCRAN des schémas (v1.64) ── */
.schema-fs-btn { position: absolute; top: 6px; right: 6px; z-index: 3; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; font-size: 0.95rem; border: 1px solid var(--border); border-radius: 6px; background: rgba(255,255,255,0.92); color: var(--muted); cursor: pointer; padding: 0; }
.schema-fs-btn:hover { border-color: var(--accent-strong); color: var(--text); }
/* (v4.64, demande utilisateur) Bouton plein écran TOUJOURS dans le coin HAUT-DROIT de l'image,
   quel que soit le format d'écran et la présence d'une barre de titre. Les barres de titre
   réservent la place à droite (padding/max-width) pour ne pas passer sous le bouton (cf. plus bas
   `.schema-title` padding-droit ; en admin la barre grise a déjà max-width: calc(100% - 40px)). */
.schema-col:has(> .schema-title) > .schema-fs-btn,
.schema-col:has(> .schema-min-head) > .schema-fs-btn { top: 6px; }
/* (v4.17) PLEIN ÉCRAN d'un schéma : le schéma s'affiche GRAND et CENTRÉ (retour utilisateur —
   avant, seul le fond blanc s'étendait, la pièce restait petite : les règles CLIENT à HAUTE
   spécificité `body.view-client #schemaTechBox > #imageSchema { width: 96% }` primaient en
   plein écran). On FORCE (`!important`, qui bat ces règles) : boîte qui remplit l'écran, centrée
   en flex, image agrandie jusqu'aux bords (96vw / 90vh) ; barre de titre et hotspots masqués. */
.schema-col:fullscreen {
  width: 100vw !important; height: 100vh !important;
  display: flex !important; align-items: center !important; justify-content: center !important;
  background: #fff !important; padding: 3vh 3vw !important; box-sizing: border-box;
}
.schema-col:fullscreen img {
  max-width: 96vw !important; max-height: 90vh !important;
  width: auto !important; height: auto !important;
  mix-blend-mode: normal !important;   /* fond blanc → le multiply est inutile */
}
.schema-col:fullscreen .schema-title,
.schema-col:fullscreen .schema-hotspots,
.schema-col:fullscreen .schema-hotspot-hint { display: none !important; }
.schema-col:fullscreen::after { display: none; }
.schema-col:fullscreen .schema-fs-btn { display: flex !important; top: 16px; right: 16px; width: 40px; height: 40px; font-size: 1.25rem; z-index: 5; }
/* (v4.64, demande utilisateur) PLEIN ÉCRAN : on GARDE le titre du schéma (barre grise admin,
   .schema-min-head), mais on retire la FLÈCHE de repli (chevron) et on neutralise le clic — le
   schéma ne doit pas se rétracter en plein écran. Le titre est épinglé en haut à gauche, inerte. */
.schema-col:fullscreen .smh-chevron { display: none !important; }
.schema-col:fullscreen .schema-min-head {
  position: absolute !important; top: 16px; left: 16px; margin: 0 !important; max-width: calc(100% - 120px);
  border-radius: var(--r-sm); box-shadow: var(--shadow-sm); z-index: 4;
  pointer-events: none !important; cursor: default !important;
}
.type-picker-title { font-size: 1.5rem; font-weight: 700; text-align: center; margin: 22px 0 4px; color: var(--text); }
.type-picker-sub { text-align: center; color: var(--muted); margin: 0 0 24px; font-size: 0.95rem; }
/* (v3.77, retour utilisateur) Tuiles AGRANDIES : le menu de sélection paraissait chétif face
   aux grandes bannières du configurateur — grille élargie (230 px mini, 1080 px max : 4 tuiles
   de type tiennent encore de front), images ~45 % plus hautes, titres un cran au-dessus. */
.type-picker-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 22px; max-width: 1080px; margin: 0 auto; }
.type-tile { display: flex; flex-direction: column; align-items: center; gap: 14px; padding: 28px 22px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); box-shadow: var(--shadow-sm); cursor: pointer; transition: transform .12s ease, border-color .12s ease, box-shadow .12s ease; }
.type-tile:hover { transform: translateY(-3px); border-color: var(--accent-strong); box-shadow: 0 8px 20px rgba(0,0,0,0.10); }
.type-tile img { width: 100%; max-width: 250px; height: 175px; object-fit: contain; background: #fbfcff; border-radius: var(--r-sm); }
/* Tuile Douille (1er menu) : template PAYSAGE (ratio ~1,9) → au lieu d'être limité en largeur (donc
   petit), on le laisse remplir la HAUTEUR du cadre comme les autres tuiles. v3.06. */
#categoryPicker .type-tile[data-cat="Douille"] img { width: auto; max-width: 100%; height: 175px; }
/* Tuiles CLÉS (v3.52) : les templates de clé sont TRÈS allongés (manche horizontal) — bornés à
   160 px de large ils devenaient minuscules. On leur laisse TOUTE la largeur de la tuile
   (object-fit:contain conserve le ratio ; le cadre de 120 px letterboxe sur fond clair). */
#categoryPicker .type-tile[data-cat="Cle"] img,
#typePickerGrid .type-tile[data-type^="cle_"] img { max-width: 100%; }
.type-tile-name { font-size: 1.25rem; font-weight: 700; color: var(--text); }   /* (v3.77) suit l'agrandissement des tuiles */
/* (v3.23) .type-tile-desc supprimée : les tuiles n'affichent plus que leur titre. */
/* (v4.37) Courte description PLACEHOLDER d'une tuile sans image (funnel embout, en attendant les
   visuels) — sous le titre, discrète. Disparaîtra dès qu'une image est fournie (meta.img). */
.type-tile-hint { font-size: 0.85rem; color: var(--muted); text-align: center; line-height: 1.35; }
/* Tuile NON CONFIGURABLE (v2.45) — ex. à cliquet : grisée + badge « sur devis ». Reste cliquable
   (ouvre la modale de contact), mais signalée comme non disponible à la configuration en ligne. */
.type-tile--soon { opacity: 0.55; filter: grayscale(0.6); }
.type-tile--soon:hover { transform: none; border-color: var(--border); box-shadow: var(--shadow-sm); }
.type-tile-badge { display: inline-block; font-size: 0.72rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--accent-strong); background: var(--accent-soft);
  border: 1px solid var(--accent-line); border-radius: 999px; padding: 2px 9px; }

/* Tag « estimé » sur les postes-hypothèses du détail devis (admin) — v1.55 */
/* (v3.64) Un cran plus LISIBLE (0,56 → 0,64 rem) et mieux détaché du libellé (marge 6 → 8 px). */
.devis-estime { font-size: 0.64rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; color: #b45309; border: 1px solid #f0c98a; background: #fff7ed; border-radius: 999px; padding: 1px 7px; margin-left: 8px; vertical-align: middle; cursor: help; }

/* ── Carte Devis en BANNIÈRE pleine largeur (bas de page, v0.79) ──
   Rangée principale horizontale : quantité · renouvellement · revient (admin) ·
   prix unitaire · prix total. */
.devis-banner {
  display: flex;
  flex-wrap: wrap;
  gap: 14px 22px;
  align-items: flex-end;
  margin-bottom: 14px;
}
.devis-banner > .field { flex: 1 1 150px; margin-bottom: 0; }
.devis-banner > .devis-renouv { flex: 2 1 260px; }

/* ── Détail des postes du devis (vue ADMIN — masqué à l'impression) ── */
.devis-detail {
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 6px 10px;
  background: #f6f8fa;
  font-size: 0.78rem;
}
.devis-detail:empty { display: none; }
/* Variante bannière : postes répartis sur plusieurs colonnes — GRILLE (auto-fill) plutôt que
   CSS columns, qui faisait chevaucher les postes dans une carte étroite. Chaque poste a une
   largeur mini ; les en-têtes de catégorie occupent toute la ligne. */
.devis-detail--banner {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
  gap: 4px 44px;
  align-items: start;
}
.devis-detail--banner .devis-cat { grid-column: 1 / -1; }
/* Chaque poste + son détail dépliable = un bloc-COLONNE : le détail reste sous SON poste, dans
   SA colonne (au lieu de retomber en pleine largeur sous la colonne de gauche). */
.devis-detail--banner .devis-poste { break-inside: avoid; min-width: 0; }
/* Ligne : libellé | heures (ou €) ÉDITABLES | coût pour la quantité */
.devis-ligne {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;   /* libellé souple | saisie | coût (auto → pas de débordement) */
  align-items: baseline;
  column-gap: 14px;
  row-gap: 2px;
  padding: 4px 0;
  color: var(--text);
}
.devis-edit {
  display: flex;
  align-items: center;
  gap: 5px;
  justify-content: flex-end;
}
.poste-input {
  width: 58px !important;
  padding: 2px 5px !important;
  font-size: 0.78rem !important;
  text-align: right;
}
.devis-unit, .devis-info {
  color: var(--muted);
  font-size: 0.72rem;
  white-space: nowrap;
}
.devis-cout {
  font-family: ui-monospace, "Cascadia Code", Consolas, monospace;
  color: var(--text);
  white-space: nowrap;
  text-align: right;
  font-variant-numeric: tabular-nums;
  padding-left: 10px;
}
/* Part en % du coût de revient : en gris, nettement détachée du montant €. */
.devis-cout .devis-pct { color: var(--muted); font-family: "Inter", system-ui, sans-serif; }
/* (v3.92) Onglet DEVIS au rythme « réglages » : filet fin sous chaque ligne de poste
   (les en-têtes de catégorie .devis-cat avaient déjà leur typo petites-caps + filet accent).
   L'éditeur (saisies, panneaux dépliables ▸) est inchangé. */
.devis-ligne { border-bottom: 1px solid var(--line); padding: 6px 0; }

/* ── Responsive ── */
/* ── Tablette / petit portable (≤ 860px) : l'en-tête (une rangée ~820px) et la barre
   d'onglets admin (7 onglets) débordent bien avant le passage en colonne unique. On les
   reflue donc plus tôt que le reste de la mise en page mobile. ── */
@media (max-width: 860px) {
  /* En-tête : passe en plusieurs rangées (titre seul, puis contrôles). */
  .app-header { flex-wrap: wrap; gap: 10px 12px; padding: 14px 18px; }
  .app-brand  { order: 1; border-right: none; padding-right: 0; }
  /* (v3.84) Le bloc titre porte la classe .app-title-group depuis la v3.82 — l'ancien
     sélecteur div:not([class]) ne matchait plus (le titre ne prenait plus sa propre ligne). */
  .app-header > .app-title-group { order: 2; flex: 1 1 100%; }
  .view-switch    { order: 3; margin-left: 0; }

  /* Onglets admin (7) : non compressés mais défilables horizontalement au doigt. */
  .admin-tabs { overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: thin; }
  .admin-tab  { flex: 0 0 auto; }
}

@media (max-width: 720px) {
  /* Marges d'écran resserrées (plus de place pour le contenu). */
  body { padding: 18px 12px 48px; }
  /* Les marges négatives du bandeau (bord à bord) doivent SUIVRE le padding du body réduit
     (-38/-20 → -18/-12), sinon l'en-tête déborde de 8 px à droite (scroll horizontal, v3.25). */
  .app-header { padding: 14px 16px; margin: -18px -12px 18px; }
  /* (v3.80) La SOUS-BARRE (confiance + avancement) suit le même resserrage — même piège v3.25. */
  .header-sub { margin: -18px -12px 20px; padding: 7px 14px; }
  .app-title    { font-size: 1.25rem; }   /* reste au-dessus des titres de section (1,14 rem) même sur mobile (v2.79) */

  /* Rangée « Sélection + schéma général » (admin) : empilée. */
  .selection-row   { flex-direction: column; }
  .general-schema  { height: 200px; }

  .layout                    { flex-direction: column; }
  .layout > .left-stack,
  .layout > .card,
  .layout > .mid-stack,
  .layout > .results-stack   { flex: none; width: 100%; }
  /* La largeur fixe de la colonne de gauche en vue admin deviendrait une HAUTEUR
     en colonne unique : on la neutralise. */
  body.view-admin .layout > .left-stack { flex: none; }
  /* Écran étroit (téléphone), onglet Pièce admin : les schémas passent EN BAS, SOUS la bannière des
     dimensions (order élevé = dernier en colonne). Sur écran large ils restent sur le côté (cf. flex:1 1 0). */
  body.view-admin.atab-piece .layout > .left-stack { order: 9; }
  .schema-stack              { position: static; }

  /* Détail du devis : une seule colonne (le mini 290px débordait) ; le libellé peut
     rétrécir (minmax(0,…)) pour ne pas pousser la ligne hors écran. */
  .devis-detail--banner { grid-template-columns: 1fr; }
  .devis-ligne          { grid-template-columns: minmax(0, 1fr) auto 56px; gap: 8px; }

  /* Cibles tactiles ≥ 44 px (recommandation mobile, v2.79) : les cases à cocher
     (16-18 px) et petits boutons (~36-40 px) étaient trop justes au doigt. */
  .checkbox-inline { min-height: 44px; align-items: center; }
  .help-btn, .btn-reselect, .btn-back { min-height: 44px; }
}

/* ── Impression : fiche récapitulative ──
   Objectif : une fiche propre (sélection + cotes + couple + prix) à imprimer/PDF-er
   pour une réunion — sans le « moteur » (formules, variables, légende).
   Les !important sont nécessaires pour neutraliser les styles INLINE posés par
   syncSchemaAlignment() (position/top/height des boîtes de schéma). */
@media print {
  body { background: #fff; padding: 0; }

  /* On n'imprime QUE le document de devis dédié (forme propre) — pas la page de config. */
  .container { display: none !important; }
  #devisPrint { display: block !important; }

  /* En-tête : version imprimable (pas de fond sombre, les navigateurs n'impriment
     pas les fonds par défaut). (v3.85) Le logo est bicolore d'origine à l'écran comme
     au papier — plus aucun filtre à neutraliser. */
  .app-header {
    background: none;
    color: #000;
    box-shadow: none;
    border-radius: 0;
    border-bottom: 2px solid #000;
    padding: 0 0 10px;
  }
  .app-brand { border-right-color: #999; }

  /* Hors fiche : formules (moteur de calcul), carte Variables, légende couleurs,
     schéma des options, et DÉTAIL des postes du devis (vue admin — le client ne
     voit que le prix) + coût de revient */
  .formula-edit,
  #cardVariables,
  .color-legend,
  .devis-detail,
  #coutRevientUnitaire,
  label[for="coutRevientUnitaire"],
  .view-switch,
  .formula-toggle,
  .label-val,
  .couple-verdict,
  .devis-actions,
  .schema-col--options { display: none !important; }

  /* Empilement vertical simple, cartes insécables */
  .selection-row, .layout, .mid-stack, .results-stack { display: block; }
  .results-stack > .card { margin-bottom: 14px; }
  .card, .schema-col {
    box-shadow: none;
    border: 1px solid #aaa;
    break-inside: avoid;
    page-break-inside: avoid;
    margin-bottom: 14px;
  }

  /* Boîtes de schéma : retour au flux normal (annule l'alignement JS inline) */
  .schema-stack { position: static !important; }
  .schema-stack .schema-col {
    position: static !important;
    top: auto !important;
    height: 300px !important;
  }
  .general-schema { height: 220px; }

  /* Badges résultats : le fond peut ne pas s'imprimer → la bordure subsiste */
  #coupleMaxNote:not(:empty), input#coutMatiere[readonly] { background: none; }
}

/* ── DOCUMENT DE DEVIS IMPRIMABLE (#devisPrint) : caché à l'écran, affiché à l'impression
   (cf. @media print). Forme « bon de devis » classique. Rempli par buildDevisPrint. ── */
#devisPrint { display: none; }
#devisPrint, #devisPrint * { box-sizing: border-box; color: #111; }
#devisPrint { font-family: "Inter", system-ui, sans-serif; font-size: 12px; line-height: 1.5; padding: 6px; }
#devisPrint .dp-head { display: flex; justify-content: space-between; align-items: flex-start; border-bottom: 2px solid #111; padding-bottom: 12px; margin-bottom: 18px; }
#devisPrint .dp-co-name { font-size: 20px; font-weight: 800; letter-spacing: 0.12em; margin-bottom: 6px; }
#devisPrint .dp-co-meta { font-size: 11px; color: #444; line-height: 1.55; }
#devisPrint .dp-title { font-size: 26px; font-weight: 700; letter-spacing: 0.04em; color: #2563eb; }
#devisPrint .dp-meta-row { display: flex; justify-content: space-between; margin-bottom: 16px; }
#devisPrint .dp-intro { margin: 0 0 16px; }
#devisPrint table { width: 100%; border-collapse: collapse; margin-bottom: 8px; }
#devisPrint th { background: #f2f4f8; text-align: left; padding: 8px 10px; border: 1px solid #cfd6e0; font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.03em; }
#devisPrint td { padding: 9px 10px; border: 1px solid #cfd6e0; vertical-align: top; }
#devisPrint .dp-num { text-align: right; white-space: nowrap; }
#devisPrint .dp-sub { color: #555; font-size: 11px; }
#devisPrint .dp-totals { width: 46%; min-width: 220px; margin-left: auto; margin-top: 6px; }
#devisPrint .dp-totals .dp-row { display: flex; justify-content: space-between; padding: 6px 10px; }
#devisPrint .dp-totals .dp-ttc { font-weight: 700; font-size: 15px; border-top: 2px solid #111; margin-top: 2px; }
#devisPrint .dp-valid { margin-top: 18px; font-size: 11px; color: #555; }
#devisPrint .dp-foot { margin-top: 36px; border-top: 1px solid #aaa; padding-top: 8px; font-size: 10px; color: #666; text-align: center; }
/* Devis multi-pièces (v1.95) */
#devisPrint .dp-section { font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: #2563eb; margin: 22px 0 8px; }
#devisPrint .dp-ref { color: #555; font-weight: 400; font-size: 11px; }
#devisPrint .dp-estim { color: #b45309; font-size: 11px; font-weight: 600; }
#devisPrint .dp-expert-row td { background: #fff7ed; }
#devisPrint .dp-estim-note { margin: 8px 0 0; font-size: 11px; color: #b45309; }

/* ════════════════════════════════════════════════════════════════════════════
   VUE CLIENT — parcours simplifié « A → suggestions → Personnaliser » (v1.34)
   ════════════════════════════════════════════════════════════════════════════ */
/* Inverse de .admin-only : visible UNIQUEMENT en vue client. */
body.view-admin .client-only { display: none !important; }

/* v3.22 : plus de case « Personnaliser » — les champs B/H/S sont DIRECTEMENT éditables (valeurs
   suggérées en gris) mais n'apparaissent qu'une fois A saisi (« les encadrés gris s'affichent
   quand on remplit A »). Idem pour la case « Options supplémentaires » sous les cotes. */
body.view-client.client-no-a #fieldB,
body.view-client.client-no-a #fieldH,
body.view-client.client-no-a #fieldS,
body.view-client.client-no-a #optionsSuppWrap { display: none !important; }
/* Manche (clés) : Largeur & Épaisseur directement éditables, révélées avec les cotes (A saisi). */
body.view-client.client-no-a #fieldMancheLargeur,
body.view-client.client-no-a #fieldMancheEpaisseur { display: none !important; }
/* (v4.01) GARDE : la case « Ajuster les cotes » ne s'affiche JAMAIS tant que A n'est pas
   saisi (body.client-no-a suit isEmpty(A) à chaque rafraîchissement) — double ceinture avec
   l'attribut hidden posé par updateClientSuggested (v3.53). */
body.view-client.client-no-a #ajusterCotesWrap { display: none !important; }

/* OPTIONS fusionnées dans la carte DIMENSIONS (v3.39, revue dirigeants) : la carte #cardOptions
   n'est plus qu'une coquille vide en vue client (grille + schéma déplacés par placeCardOptions
   sous la case « Options supplémentaires ») → toujours masquée. La grille et le schéma ne
   s'affichent que si la case est cochée (body.options-supp-on, posé par setOptionsSupp). */
body.view-client #cardOptions { display: none !important; }
body.view-client:not(.options-supp-on) #optionsGrid { display: none !important; }
body.view-client:not(.options-supp-on) #optionsSchemaBox { display: none !important; }
/* Dans la grille 2 colonnes des cotes, la grille des options et son schéma prennent TOUTE la
   largeur (au-dessus du manche/couple), comme les éléments .field-full. */
body.view-client #cardDimensions .grid > #optionsGrid,
body.view-client #cardDimensions .grid > #optionsSchemaBox { grid-column: 1 / -1; }
.suggested-values { display: flex; flex-wrap: wrap; gap: 8px; }
.sv-chip { display: inline-flex; align-items: baseline; gap: 5px; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r-sm); padding: 5px 11px; }
/* (v4.03) Pastilles CLIQUABLES (data-fld → « Ajuster les cotes » + focus du champ) :
   curseur + survol accent pour signifier l'affordance. */
.sv-chip[data-fld] { cursor: pointer; transition: border-color 0.12s, background 0.12s, box-shadow 0.12s; }
.sv-chip[data-fld]:hover, .sv-chip[data-fld]:focus-visible { border-color: var(--accent); background: var(--accent-soft); box-shadow: 0 0 0 2px var(--accent-soft); }
.sv-chip-k { font-size: 0.72rem; font-weight: 600; color: var(--muted); }
.sv-chip-v { font-size: 1rem; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
.sv-chip-u { font-size: 0.72rem; color: var(--muted); }
/* Pastille d'OPTION autocomplétée (pièce réalisée) : accentuée (bleu) pour la distinguer des cotes. */
.sv-chip--opt { background: var(--accent-soft); border-color: var(--accent-line); }
.sv-chip--opt .sv-chip-k, .sv-chip--opt .sv-chip-v { color: var(--accent-strong); }
.suggested-values:empty { display: none; }
/* Réf UN de la pièce déjà réalisée, à côté des cotes (vue client, v2.52) : pastille accentuée. */
/* Réf UN à DROITE du titre « Cotes » (v2.55) : le divider passe en flex, la pastille est poussée
   à droite ; les pastilles de cotes B/H/S (dans la ligne de A) restent ainsi alignées. */
.section-divider:has(.realise-ref) { display: flex; align-items: center; }
/* (v3.61) Note « Petite ouverture, valeurs suggérées surévaluées » À CÔTÉ du titre « Cotes »
   (demande utilisateur — elle vivait sous le champ A). Ton .field-warn (orange discret, italique),
   en RESET de la typo du divider (capitales espacées) ; le divider est déjà flex (:has ci-dessus). */
.divider-warn { margin-left: 12px; font-size: 0.78rem; font-weight: 500; font-style: italic;
  color: #f97316; text-transform: none; letter-spacing: normal; }
.divider-warn[hidden] { display: none; }
.realise-ref { margin-left: auto; font-size: 0.78rem; font-weight: 600; color: var(--accent-strong);
  background: var(--accent-soft); border: 1px solid var(--accent-line); border-radius: 999px;
  padding: 3px 10px; white-space: nowrap; letter-spacing: 0; }
.realise-ref:empty { display: none; }
/* Note « double clé → contact » en bas de la carte Dimensions (vue client, clés). v2.52. */
.dim-cle-double { margin: 12px 0 0; padding: 9px 12px; font-size: 0.85rem; color: var(--text);
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r-sm); line-height: 1.45; }
.dim-cle-double a { color: var(--accent-strong); font-weight: 600; white-space: nowrap; }

/* ── Aperçu des OPTIONS d'une pièce réalisée, sous la bannière Dimensions (vue client) ──
   Affiché si une option est autocomplétée (body.auto-options-on) ET tant que le panneau
   d'options n'est pas ouvert ; disparaît quand « Options supplémentaires » est coché. ── */
.auto-options-preview { display: none; }
body.view-client.auto-options-on:not(.options-supp-on) .auto-options-preview { display: block; margin-top: 14px; }
.auto-options-preview .aop-head { font-size: 0.85rem; font-weight: 600; color: var(--accent-strong); margin-bottom: 8px; }
.auto-options-preview .aop-head #autoOptionsVals { font-weight: 700; }
.auto-options-preview .schema-col--options { height: 240px; }
/* ── HARMONISATION DES AUTOCOMPLÉTIONS (v3.53, renverse la v3.22) : les cotes autocomplétées
   s'affichent D'ABORD en PASTILLES lecture seule (petits encadrés clairs .sv-chip) ; la case
   « Ajuster les cotes » (body.perso-on / manche-perso-on / douille-perso-on) dévoile les
   champs éditables. Vue client seulement — l'admin garde ses champs en permanence. ── */
body.view-client.perso-on #suggestedValues { display: none; }          /* mode ajusté → les champs parlent */
body.view-client.manche-perso-on #mancheSuggested { display: none; }
body.view-admin #suggestedValues, body.view-admin #mancheSuggested { display: none; }
/* Mode PASTILLES (case décochée) : les champs de cotes autocomplétées sont masqués. */
body.view-client:not(.perso-on) #fieldB,
body.view-client:not(.perso-on) #fieldH,
body.view-client:not(.perso-on) #fieldS { display: none !important; }
body.view-client:not(.manche-perso-on) #fieldMancheLargeur,
body.view-client:not(.manche-perso-on) #fieldMancheEpaisseur { display: none !important; }
/* (v3.57) La case « Options supplémentaires » n'apparaît qu'en mode AJUSTÉ — en mode
   pastilles le parcours reste minimal (décocher « Ajuster les cotes » referme aussi les
   options, cf. setAjusterCotes). (v4.01) La 2e case côté manche (#optionsSuppMancheWrap,
   v2.58) est SUPPRIMÉE du HTML — celle des cotes suffit. */
body.view-client:not(.perso-on) #optionsSuppWrap { display: none !important; }
/* (v3.93) Invite « Saisissez une ouverture » posée À LA PLACE de la case « Ajuster les
   cotes » quand on clique une cote du schéma (hotspot) sans avoir saisi A (goToField) —
   masquée dès que A est rempli. Orange warn : c'est la réponse à une action bloquée. */
.ajuster-hint { margin: 0; font-size: 0.9rem; font-weight: 600; color: var(--warn); }

/* (Personnaliser, v4.28) BOUTON dépliable UNIQUE « Personnaliser mon outil » (vue client) :
   remplace les deux cases « Ajuster les cotes » / « Options supplémentaires ». La case
   #toggleAjusterCotes reste fonctionnelle mais masquée ; le label EST le bouton. */
.personnaliser-toggle { display: flex; align-items: center; gap: 12px; cursor: pointer;
  padding: 11px 14px; margin: 6px 0 2px; border: 1px solid var(--accent-line); border-radius: var(--r-sm);
  background: var(--accent-soft); color: var(--brand-navy); transition: background 0.15s, border-color 0.15s; }
.personnaliser-toggle:hover { background: #e3eef8; border-color: var(--accent); }
.personnaliser-toggle input { position: absolute; opacity: 0; width: 0; height: 0; }   /* case masquée mais fonctionnelle */
.personnaliser-toggle .pt-ico { width: 18px; height: 18px; flex: 0 0 auto; color: var(--accent); }
.personnaliser-toggle .pt-text { display: flex; flex-direction: column; gap: 1px; flex: 1 1 auto; min-width: 0; }
.personnaliser-toggle .pt-title { font-weight: 700; font-size: 0.95rem; }
.personnaliser-toggle .pt-hint { font-weight: 400; font-size: 0.78rem; color: var(--muted); }
.personnaliser-toggle .pt-chevron { width: 18px; height: 18px; flex: 0 0 auto; color: var(--accent); transition: transform 0.2s; }
.personnaliser-toggle:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Ouvert (panneau déplié) : accent plein + chevron pivoté. Piloté par body.perso-on (fiable ;
   la case #toggleAjusterCotes étant masquée, on n'utilise pas :checked). */
body.view-client.perso-on .personnaliser-toggle,
body.view-client.douille-perso-on .personnaliser-toggle { background: var(--accent); border-color: var(--accent); color: #fff; }
body.view-client.perso-on .personnaliser-toggle .pt-ico,
body.view-client.perso-on .personnaliser-toggle .pt-chevron,
body.view-client.douille-perso-on .personnaliser-toggle .pt-ico,
body.view-client.douille-perso-on .personnaliser-toggle .pt-chevron { color: #fff; }
body.view-client.perso-on .personnaliser-toggle .pt-hint,
body.view-client.douille-perso-on .personnaliser-toggle .pt-hint { color: rgba(255, 255, 255, 0.85); }
body.view-client.perso-on .personnaliser-toggle .pt-chevron,
body.view-client.douille-perso-on .personnaliser-toggle .pt-chevron { transform: rotate(180deg); }

/* Sous-titres du panneau (Ajuster les cotes / Options supplémentaires) : visibles seulement
   en mode Personnaliser (body.perso-on). Structurent le panneau déplié en deux groupes. */
.personnaliser-divider { display: none; }
body.view-client.perso-on .personnaliser-divider { display: block; margin: 14px 0 6px;
  font-size: 0.78rem; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 700;
  color: var(--muted); border-top: 1px solid var(--border); padding-top: 12px; }

/* (v4.29) EMPREINTE déplacée AVEC LES COTES (œil/tuyauter) : la pastille suffit par défaut ; le
   MENU DÉROULANT n'apparaît que dans « Personnaliser » (body.perso-on), comme B/H/S. */
body.view-client #fieldEmpreinte:not([hidden]) { display: none; }
body.view-client.perso-on #fieldEmpreinte:not([hidden]) { display: flex; align-items: center; gap: 10px; }
body.view-client.client-no-a #fieldEmpreinte:not([hidden]) { display: none !important; }
body.view-client #fieldEmpreinte > label { margin: 0; font-weight: 600; flex: none; min-width: 14px; }
body.view-client #fieldEmpreinte .empreinte-sel { font: inherit; padding: 6px 10px; border: 1px solid var(--border);
  border-radius: var(--r-sm); background: var(--card); color: var(--text); cursor: pointer; }
body.view-client #fieldEmpreinte .empreinte-perso { flex: 1 1 auto; min-width: 0; }
/* « Options supplémentaires » : TRÈS VISIBLE (l'outil est fait pour ça)
   → encre foncée, gras, taille augmentée, case en couleur d'accent. */
.checkbox-inline { display: inline-flex; align-items: center; gap: 8px; font-size: 0.95rem; font-weight: 600; color: var(--text); white-space: nowrap; cursor: pointer; }
.checkbox-inline input { margin: 0; width: 16px; height: 16px; accent-color: var(--accent); cursor: pointer; }
/* HITBOX (v1.91) : un label-case en `field-full` (grid-column 1/-1) s'étire sur toute la largeur par
   défaut (justify-self: stretch) → on coche en cliquant loin du texte. On le ramène à la largeur du
   CONTENU (case + libellé) avec justify-self: start. Idem pour « Personnaliser » (sécurité). */
.checkbox-inline.field-full { justify-self: start; }

/* Statut de faisabilité (vue client) : pastille verte « réalisable » / orange « à ajuster ». */
.feas-badge { display: inline-flex; align-items: center; gap: 8px; font-size: 0.85rem; font-weight: 600; padding: 7px 13px; border-radius: 999px; }
.feas-badge:empty { display: none; }
.feas-badge::before { content: ""; width: 8px; height: 8px; border-radius: 50%; flex: none; }
.feas-badge.is-no { background: #fdeee6; color: var(--warn); }
.feas-badge.is-no::before { background: var(--warn); }
.feas-badge.is-alert { background: #fdecec; color: var(--err); }
.feas-badge.is-alert::before { background: var(--err); }

/* Alertes bloquantes à côté de « Obtenir mon devis » (v1.81) : EMPILÉES (pas de chevauchement),
   même format orange que le badge de faisabilité, multi-lignes. */
.devis-alerts { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
.devis-alerts:empty { display: none; }
.devis-alert { display: flex; align-items: flex-start; gap: 9px; font-size: 0.85rem; font-weight: 600; line-height: 1.3; padding: 9px 14px; border-radius: 12px; background: #fdeee6; color: var(--warn); }
.devis-alert::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: var(--warn); flex: none; margin-top: 4px; }
/* Alerte « revue expert » (v1.87) — ambre info, NON bloquante pour la commande (distincte du rouge). */
.devis-alert--expert { background: #fff7e6; color: #92600a; }
.devis-alert--expert::before { background: #d97706; }
/* Mention sous le prix quand la pièce passe en revue expert. */
.price-indicative { display: block; margin-top: 4px; font-size: 0.78rem; font-style: italic; color: var(--warn); }
.price-indicative[hidden] { display: none; }
/* Mention « Brut trop grand » sous le Prix total (brut hors grille prix acier). v2.52. */
.price-toobig { display: block; margin-top: 4px; font-size: 0.78rem; font-weight: 600; color: var(--err); }
.price-toobig[hidden] { display: none; }
/* Bouton « Obtenir mon devis » grisé/non cliquable tant qu'une alerte bloque. */
.hero-cta:disabled { background: #cbd5e1; color: #eef2f6; cursor: not-allowed; box-shadow: none; }
.hero-cta:disabled:hover { filter: none; }


/* ── Unité intégrée au champ (mm, N·m) — la bordure passe sur le wrapper, l'input devient
   nu ; suffixe gris à droite. Masqué en vue admin (densité). ── */
/* Empreinte (œil & tuyauter, v1.80) : libellé « Empreinte » SUR SA LIGNE, puis les 3 options
   EN DESSOUS (retour à la ligne), et le champ texte (si « Personnalisée ») encore en dessous.
   On force le bloc en vue client (sinon la règle flex de #optionsGrid met tout sur une ligne). */
body.view-client #optionsGrid #fieldEmpreinte { display: block; }
body.view-client #optionsGrid #fieldEmpreinte > label { display: block; text-align: left; min-width: 0; padding-top: 0; margin-bottom: 6px; }
.empreinte-choices { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
.empreinte-opt { display: inline-flex; align-items: center; gap: 7px; font-size: 0.85rem; color: var(--text); cursor: pointer; font-weight: 400; }
.empreinte-opt input { accent-color: var(--accent, #15567f); cursor: pointer; margin: 0; }
.empreinte-perso { margin-top: 9px; max-width: 100%; width: 100%; }
.empreinte-perso[hidden] { display: none; }   /* prime sur .input-unit{display:flex} (sinon toujours visible) */
.empreinte-perso input[type="text"] { border: none !important; box-shadow: none !important; width: 100%; padding: 9px 13px; font: inherit; font-size: 0.9rem; background: transparent; }
.empreinte-perso.empreinte-required { border-color: var(--err, #dc2626); box-shadow: 0 0 0 2px rgba(220,38,38,.14); }
/* Vue ADMIN (v1.87) : on n'affiche que la VALEUR choisie (pas les radios ni le champ),
   en libellé + PASTILLE bien aérés. */
/* Force le FLEX (sinon écrasé par la grille des options en admin → chevauchement libellé/pastille). */
/* :not([hidden]) — sinon le `display:flex !important` écrase l'attribut `hidden` et l'empreinte
   reste visible là où elle n'a pas lieu d'être (clé/embout à fourche — pas d'empreinte). v2.30 */
/* (v4.66, demande utilisateur) EMPREINTE (admin, onglet Pièce) : alignée sur la GRILLE des cotes —
   libellé (col 1), valeur choisie (col 2), menu d'ajustement RACCOURCI (col 3) — au lieu d'occuper
   toute la largeur (le menu passait sous le libellé). */
body.view-admin #fieldEmpreinte:not([hidden]) {
  display: grid !important; grid-template-columns: 82px minmax(0, 1fr) minmax(0, 1fr);
  column-gap: 22px; row-gap: 4px; align-items: start;   /* (v5.15) gap 22 harmonisé */
}
body.view-admin #fieldEmpreinte > label { grid-column: 1; align-self: start; padding-top: 7px; margin: 0 !important; white-space: nowrap; justify-self: end; text-align: right; }
body.view-admin #fieldEmpreinte > .empreinte-value { grid-column: 2; align-self: start; }
body.view-admin #fieldEmpreinte > .empreinte-sel { grid-column: 3; grid-row: 1; width: 100%; }
.empreinte-value {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 2px 11px; border-radius: 999px; line-height: 1.6;
  background: var(--accent-soft); border: 1px solid var(--accent-line);
  color: var(--accent-strong); font-weight: 600; font-size: 0.82rem;
}
.empreinte-value:empty { display: none; }
/* « Personnalisée » → pastille d'ATTENTION (orange) : pièce particulière (revue expert, cf. devis). */
.empreinte-value.is-perso { background: #fff4ec; border-color: #f0c9a8; color: var(--warn); }
body.view-admin #fieldEmpreinte #empreinteChoices,
body.view-admin #fieldEmpreinte #empreintePersoWrap { display: none; }

.input-unit { display: flex; align-items: stretch; border: 1px solid var(--border); border-radius: var(--r-sm); background: #fff; overflow: hidden; transition: border-color 0.15s, box-shadow 0.15s; }
.input-unit input[type="number"] { border: none !important; box-shadow: none !important; border-radius: 0; }
.input-unit:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.input-unit:has(input.input-negative),
.input-unit:has(input.cote-aberrant) { border-color: var(--err); box-shadow: 0 0 0 3px rgba(220,38,38,.12); }
.field-unit { display: flex; align-items: center; padding: 0 11px; font-size: 0.78rem; font-weight: 500; color: var(--muted); background: var(--surface-2); border-left: 1px solid var(--border); white-space: nowrap; }
/* Sélecteur d'unité mm/pouce DANS le champ (douille A, v2.95) : même allure que .field-unit, cliquable,
   COMPACT (largeur auto) — c'est le champ qui prend l'espace restant. */
.field-unit-sel { flex: 0 0 auto; width: auto; border: none; border-left: 1px solid var(--border); background: var(--surface-2); color: var(--muted); font: inherit; font-size: 0.78rem; font-weight: 600; padding: 0 6px; cursor: pointer; border-radius: 0; }
.field-unit-sel:focus { outline: none; box-shadow: 0 0 0 2px var(--accent-soft) inset; }
#fieldDouilleA .input-unit > input, #fieldDouilleCarrePerso .input-unit > input { flex: 1 1 auto; min-width: 0; }   /* le champ grandit, l'unité reste compacte */
#douilleA, #douilleCarrePerso { border: none !important; box-shadow: none !important; border-radius: 0; }   /* champs texte : la bordure vient de .input-unit */
.douille-a-hint { display: block; margin-top: 3px; font-size: 0.78rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.douille-a-hint:empty { display: none; }
/* L'unité vit désormais DANS le champ (les libellés A/B/H/S/Couple ne la portent plus) :
   elle s'affiche donc dans LES DEUX vues (client ET admin). */
/* B/H/S = field-input-wrap + input-unit : `.field-input-wrap { display:block }` (plus bas)
   écraserait le flex de `.input-unit` → on le rétablit pour aligner [champ | unité]. */
.field-input-wrap.input-unit { display: flex; }
/* Cote = colonne : [champ] en haut, mention « (suggérée) » / message d'angle EN DESSOUS. */
.cote-col { display: flex; flex-direction: column; min-width: 0; }
/* Message d'angle (α/β/γ) : pas de marge parasite, et COLLAPSE quand vide (sinon il
   creuse un écart non uniforme sous α/β/γ). */
.cote-col > p { margin: 3px 0 0; }
.cote-col > p:empty { display: none; }

/* ── Cotes en LIGNE (vue client) : « A = [champ court mm] », libellé vertical à gauche + « = ».
   L'unité étant en bout de champ, le libellé n'est plus que le nom de la cote. ── */
body.view-client #fieldA,
body.view-client #fieldB,
body.view-client #fieldH,
body.view-client #fieldS,
body.view-client #optionsGrid .field { display: flex; align-items: flex-start; gap: 10px; }
body.view-client #fieldA > label,
body.view-client #fieldB > label,
body.view-client #fieldH > label,
body.view-client #fieldS > label,
body.view-client #optionsGrid .field > label { margin: 0; padding-top: 10px; flex: none; min-width: 14px; text-align: right; font-weight: 600; }
body.view-client #fieldA > label::after,
body.view-client #fieldB > label::after,
body.view-client #fieldH > label::after,
body.view-client #fieldS > label::after,
body.view-client #optionsGrid .field:not(#fieldEmpreinte) > label::after { content: " ="; color: var(--muted); font-weight: 400; }
/* (v3.94) Champs de COTES raccourcis (retour utilisateur : « champs plus courts, y compris
   celui de A ») : 120 px suffisent pour 4 chiffres + le segment d'unité — condition de la
   rangée B|H|S à 3 colonnes ci-dessous. Les OPTIONS (bannière à part) gardent 150 px. */
body.view-client #fieldA .input-unit { flex: none; width: 120px; }
/* (v3.95) B/H/S en MINI-GRILLE « libellé | champ » (remplace la ligne flex de la règle
   au-dessus, cascade) : dans une colonne fluide devenue étroite, la ligne flex REPLIAIT le
   champ sous son libellé (le wrap prime sur le rétrécissement) — la grille, elle, RÉTRÉCIT
   le champ (max-width) sans jamais replier. Libellé col. 1 ; champ, aide et messages
   dynamiques col. 2 (alignés sous le champ). */
body.view-client #fieldB,
body.view-client #fieldH,
body.view-client #fieldS { display: grid; grid-template-columns: max-content minmax(0, 1fr); column-gap: 8px; row-gap: 2px; align-items: start; justify-items: start; }
body.view-client #fieldB > :not(label),
body.view-client #fieldH > :not(label),
body.view-client #fieldS > :not(label) { grid-column: 2; }
body.view-client #fieldB .input-unit,
body.view-client #fieldH .input-unit,
body.view-client #fieldS .input-unit { width: 120px; max-width: 100%; }
body.view-client #fieldB .input-unit > input,
body.view-client #fieldH .input-unit > input,
body.view-client #fieldS .input-unit > input { flex: 1 1 auto; min-width: 0; width: auto; }
/* (v4.28) OPTIONS au MÊME FORMAT que les cotes : rangées de 3, champs 120 px, mini-grille
   « libellé | champ » — le panneau « Personnaliser » présente cotes ET options à l'identique. */
body.view-client #optionsGrid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px 12px; }
body.view-client #optionsGrid > .field:not(#fieldEmpreinte) { display: grid; grid-template-columns: max-content minmax(0, 1fr); column-gap: 8px; row-gap: 2px; align-items: start; justify-items: start; margin-bottom: 0; }
body.view-client #optionsGrid > .field:not(#fieldEmpreinte) > :not(label) { grid-column: 2; }
body.view-client #optionsGrid .field .input-unit { flex: none; width: 120px; max-width: 100%; }
body.view-client #optionsGrid .field .input-unit > input { flex: 1 1 auto; min-width: 0; width: auto; }
/* Empreinte (radios), titre « Options » et schéma : pleine largeur de la grille. */
body.view-client #optionsGrid > .section-divider,
body.view-client #optionsGrid > #fieldEmpreinte,
body.view-client #optionsGrid > #optionsSchemaBox { grid-column: 1 / -1; }
@media (max-width: 560px) { body.view-client #optionsGrid { grid-template-columns: 1fr; } }
/* Exception : l'empreinte « Personnalisée » est un champ TEXTE → largeur pleine. v1.90. */
body.view-client #optionsGrid #fieldEmpreinte .empreinte-perso { width: 100%; flex: none; }

/* ── Manche : LONGUEUR (vue client, v2.46) ── « Longueur » reste AU-DESSUS, puis une ligne
   « L = [champ court] mm » façon cote A (préfixe « L » + « = », champ borné, valeur en gras). */
body.view-client #fieldMancheLongueur { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
body.view-client #fieldMancheLongueur > label { flex-basis: 100%; margin: 0; font-weight: 600; }
body.view-client .manche-l-prefix { flex: none; min-width: 14px; text-align: right; font-weight: 600; color: var(--text); }
body.view-client .manche-l-prefix::after { content: " ="; color: var(--muted); font-weight: 400; }
body.view-client #fieldMancheLongueur .input-unit { flex: none; width: 120px; }   /* (v3.94) raccourci, comme A */
body.view-client #fieldMancheLongueur input[type="number"] { font-weight: 700; }

/* ── Pastilles ALIGNÉES sur le champ déclencheur et COLLÉES à sa droite (v3.55, resserre la
   v3.54 : en colonne 2 de la grille, elles restaient à ~95 px de l'input et centrées sur la
   hauteur de la cellule). #fieldA est une LIGNE flex (label · input · …) : les pastilles y
   reprennent leur place SUR la ligne de l'input, à 10 px — et #fieldA passe en PLEINE LARGEUR
   en mode pastilles (les champs B/H/S de la colonne 2 sont masqués, la place est libre).
   Manche (clés) : même mécanique dans la ligne de la Longueur. Sous 560 px : repli dessous. */
/* (v3.95) Pleine largeur dans les DEUX modes (retour utilisateur) : en mode ajusté aussi,
   « A = [champ] » garde sa rangée entière — dans une colonne étroite (fenêtre non maximisée),
   le champ passait SOUS le libellé « A = » ; et la case « Ajuster les cotes » (field-full,
   rangée suivante) ne BOUGE plus quand on la coche. */
body.view-client #fieldA { grid-column: 1 / -1; }
body.view-client #fieldA .suggested-values { flex: 1 1 auto; min-width: 0; align-self: center; }   /* le gap flex (10 px) suffit */
body.view-client #fieldA > .douille-a-hint,
body.view-client #fieldA > .field-help { flex-basis: 100%; }   /* repère pouces + aide : sous la ligne */
body.view-client #fieldMancheLongueur .suggested-values { flex: 1 1 auto; min-width: 0; align-self: center; }
@media (max-width: 560px) {
  body.view-client #fieldA .suggested-values,
  body.view-client #fieldMancheLongueur .suggested-values { flex: 0 0 100%; margin: 6px 0 0; }
}

/* Messages sous les champs de cotes (« Petite ouverture… », garde-fous B≥tenon, A<B…) — v3.38/39 :
   les .field sont des LIGNES flex (label + champ) → sans retour à la ligne, le <p> s'écrasait À
   DROITE du champ (1 mot par ligne). On autorise le wrap et le message prend toute la largeur,
   aligné sous le champ (24px = label 14 + gap 10) ; police .field-warn/.field-error, cohérente. */
body.view-client #fieldA,
body.view-client #fieldB,
body.view-client #fieldH,
body.view-client #fieldS,
body.view-client #fieldMancheLongueur { flex-wrap: wrap; }   /* (v3.53) les pastilles du manche passent dessous */
body.view-client #fieldA > .field-warn,  body.view-client #fieldA > .field-error,
body.view-client #fieldB > .field-warn,  body.view-client #fieldB > .field-error,
body.view-client #fieldH > .field-warn,  body.view-client #fieldH > .field-error,
body.view-client #fieldS > .field-warn,  body.view-client #fieldS > .field-error { flex-basis: 100%; margin: 2px 0 0 24px; }

/* Champ A MIS EN AVANT (vue client) : c'est l'entrée principale du parcours. */
body.view-client #fieldA > label { color: var(--accent-strong); }
/* (v3.94) 165 → 130 px (retour utilisateur « champs plus courts, y compris A ») : le sélecteur
   d'unité mm/″ étant plus large que le segment « mm », la partie saisie s'aligne sur B/H/S. */
body.view-client #fieldA .input-unit { border-color: var(--accent-line); width: 130px; }
body.view-client #fieldA input[type="number"] { font-size: 1.05rem; font-weight: 600; padding-top: 11px; padding-bottom: 11px; }

/* Barre de titre « plan » du schéma technique (vue client). */
.schema-title { position: absolute; top: 0; left: 0; right: 0; z-index: 2; font-size: 0.78rem; font-weight: 600; color: var(--text); background: rgba(255,255,255,0.94); border-bottom: 1px solid var(--line); border-radius: var(--r) var(--r) 0 0; padding: 7px 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.schema-title:empty { display: none; }

/* État vide guidé (vue client) : avant saisie de A (body.client-no-a) — invite, pas de résumé. */
.client-empty-hint { display: none; }
body.view-client.client-no-a .client-empty-hint { display: block; color: var(--muted); font-size: 0.9rem; padding: 6px 2px; }

/* ── FICHE RÉCAP (vue client) : datasheet de la configuration, remplace le formulaire « brut » ── */
.devis-recap { margin-bottom: 18px; }
.recap-specs { display: flex; flex-direction: column; }
.recap-specs:empty { display: none; }
/* Plan de la pièce déjà réalisée, affiché dans le devis (v1.91). */
.devis-realise-plan { margin: 14px 0; }
.devis-realise-plan-title { font-size: 0.85rem; font-weight: 600; color: var(--muted); margin-bottom: 6px; }
.devis-realise-plan img { display: block; max-width: 100%; max-height: 340px; border: 1px solid var(--border); border-radius: 8px; background: #fff; padding: 6px; }

/* ── PANIER / COMMANDE MULTI-PIÈCES (v1.95) ── */
.devis-cta-row { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.hero-cta--add { background: var(--surface-2); color: var(--accent-strong); border: 1px solid var(--accent-line); }
.hero-cta--add:hover { background: var(--accent-soft); }
.cart-columns { display: flex; flex-wrap: wrap; gap: 18px; margin: 18px 0 6px; }
.cart-col { flex: 1 1 240px; min-width: 220px; }
.cart-col-title { font-size: 0.82rem; font-weight: 700; margin: 0 0 8px; padding-bottom: 5px; border-bottom: 2px solid var(--border); color: var(--text); }
.cart-col--expert .cart-col-title { color: #b45309; border-bottom-color: #f0c98a; }
.cart-card { position: relative; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r-sm); padding: 10px 28px 10px 12px; margin-bottom: 8px; }
.cart-col--expert .cart-card { background: #fff7ed; border-color: #f0c98a; }
.cart-card-desig { font-weight: 600; font-size: 0.9rem; }
.cart-card-cotes { font-size: 0.78rem; color: var(--muted); margin-top: 2px; }
.cart-card-ref { font-size: 0.78rem; color: var(--accent-strong); margin-top: 2px; }
.cart-card-qty { font-size: 0.82rem; color: var(--muted); margin-top: 3px; }   /* (v4.32) quantité toujours affichée */
.cart-card-price { display: block; font-weight: 700; margin-top: 4px; }
.cart-card-del { position: absolute; top: 4px; right: 6px; border: none; background: none; color: var(--muted); font-size: 1.14rem; line-height: 1; cursor: pointer; padding: 2px 5px; border-radius: var(--r-sm); }
.cart-card-del:hover { color: var(--err); background: #fdecec; }
/* Panier non vide : forcer la bannière devis visible (même au sélecteur de type — #cardDevis est hors .layout). */
body.view-client.cart-active #cardDevis { display: block !important; }
/* Mode « panier seul » (aucune pièce courante en cours) : masquer récap/banner de la pièce courante. */
body.view-client.cart-only #devisRecap,
body.view-client.cart-only .devis-banner { display: none; }
/* (v3.64) SORTIE DE L'IMPASSE panier-seul : sur l'écran des TUILES (la zone de configuration —
   et son « Obtenir mon devis » — est masquée), la carte Devis porte le CTA #cartCta ; et le
   message « Prix indisponible… » (qui parle de la pièce VIDE suivante) est masqué — anxiogène
   sous un panier plein. */
#cartCta { display: none; text-align: center; padding: 14px 0 4px; }
body.view-client.cart-only.client-no-cat #cartCta,
body.view-client.cart-only.client-no-type #cartCta { display: block; }
body.view-client.cart-only #devisNote { display: none; }
.cart-cta-note { margin: 0 0 10px; font-size: 0.9rem; color: var(--text); }

/* ── SCÈNES ADMIN : commandes / pièces / détail (v1.95) ── */
.admin-scene { display: none; }
body.view-admin.asc-orders #ordersScene { display: block; }
body.view-admin.asc-pieces #piecesScene { display: block; }
body.view-admin.asc-accounts #accountsScene { display: block; }   /* (v4.84) scène Comptes clients */
/* En scènes commandes/pièces : masquer le détail (sélection + layout + devis).
   (v5.15) #adminTabs est AUSSI masqué sur l'inbox : la raison v3.64 (onglets communs
   Catalogue/Prix acier accessibles sans pièce) est obsolète — ces référentiels vivent dans la
   SIDEBAR (Phase 2b) et la barre ne porte plus que les 3 onglets de pièce → elle s'affichait
   VIDE (le « trait blanc » signalé). Doublé d'une RAZ de viewedPiece dans routeGotoInbox. */
body.view-admin.asc-orders #adminTabs, body.view-admin.asc-orders .selection-row,
body.view-admin.asc-orders .layout, body.view-admin.asc-orders #cardDevis, body.view-admin.asc-orders #selectionSummary,
body.view-admin.asc-pieces #adminTabs, body.view-admin.asc-pieces .selection-row,
body.view-admin.asc-pieces .layout, body.view-admin.asc-pieces #cardDevis, body.view-admin.asc-pieces #selectionSummary,
body.view-admin.asc-accounts #adminTabs, body.view-admin.asc-accounts .selection-row,
body.view-admin.asc-accounts .layout, body.view-admin.asc-accounts #cardDevis, body.view-admin.asc-accounts #selectionSummary { display: none !important; }
/* (v3.71) L'onglet VARIABLES doit s'afficher MÊME SANS PIÈCE (accès depuis l'inbox, v3.64) :
   #cardVariables porte nl-hidden (dévoilée par la sélection progressive, qui ne tourne jamais
   sans type choisi) → en admin, sur son onglet, la carte s'affiche quoi qu'il en soit — c'est
   l'onglet qui gouverne, pas le formulaire progressif. (Catalogue/Prix acier/Gamme & taux ne
   portent pas nl-hidden.) */
body.view-admin.atab-parametres #cardVariables.nl-hidden { display: block !important; }
/* (v3.70) Les onglets PAR PIÈCE (Pièce/Usinage/Devis + leur séparateur) ne s'affichent que si
   une pièce est OUVERTE (body.admin-no-piece posé par setAdminScene) — dans l'inbox comme dans
   un détail atteint via un onglet commun. « Récapitulatif des devis » et les onglets communs
   restent toujours visibles. */
body.view-admin.asc-orders #adminTabs .admin-tab:not(.admin-tab--common):not(.admin-tab--recap),
body.view-admin.asc-orders #adminTabs .tab-sep,
body.view-admin.admin-no-piece #adminTabs .admin-tab:not(.admin-tab--common):not(.admin-tab--recap),
body.view-admin.admin-no-piece #adminTabs .tab-sep { display: none; }
/* (v4.02) Et réciproquement : « Récapitulatif des devis » DISPARAÎT quand une pièce est
   ouverte (demande utilisateur) — le « ← Retour » au-dessus des onglets couvre la remontée. */
body.view-admin:not(.admin-no-piece) #adminTabs .admin-tab--recap { display: none; }
/* (v5.10) La barre BASSE du détail (.detail-bar--bottom, miroir v3.64) est SUPPRIMÉE : les
   actions de validation vivent uniquement dans le fil d'Ariane (haut de page). */
/* (v3.64) Tri de l'inbox : petit libellé + select compact à droite du titre. */
.orders-sort-lab { display: inline-flex; align-items: center; gap: 8px; font-size: 0.82rem; color: var(--muted); font-weight: 600; }
.orders-sort-lab select { width: auto; padding: 6px 10px; font-size: 0.85rem; }
.btn-back { background: none; border: none; color: var(--accent-strong); font: inherit; font-size: 0.9rem; font-weight: 600; cursor: pointer; padding: 6px 4px; margin-bottom: 10px; }
/* (v4.02) Flèche « ← » nettement plus grande, comme celle des « ← Changer de… » (.picker-back). */
.btn-back::first-letter { font-size: 1.55em; }
.btn-back:hover { text-decoration: underline; }
.scene-head { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; margin-bottom: 16px; }
.scene-title { margin: 0; font-size: 1.25rem; }
.scene-empty { color: var(--muted); font-size: 0.9rem; padding: 24px 0; }
/* (v3.91) État VIDE avec pictogramme (revue design « pro », point 5) : icône 28 px centrée
   estompée + phrase d'invitation — page panier (pièces/devis) et inbox admin. */
.empty-state { text-align: center; padding: 24px 0 16px; color: var(--muted); }
.empty-state svg { display: block; margin: 0 auto 9px; opacity: 0.5; }
.empty-state p { margin: 0 auto; font-size: 0.9rem; max-width: 460px; line-height: 1.5; }
/* (v4.03) font: inherit — ces <button> restaient en police PAR DÉFAUT du navigateur
   (Arial ~13px) au lieu de celle de l'app (retour utilisateur : « formats différents »). */
.order-card, .piece-card { display: block; width: 100%; font: inherit; text-align: left; background: var(--card); border: 1px solid var(--border); border-radius: var(--r); padding: 14px 16px; margin-bottom: 10px; cursor: pointer; box-shadow: var(--shadow-sm); transition: border-color .12s, box-shadow .12s; }
.order-card:hover, .piece-card:hover { border-color: var(--accent); box-shadow: var(--shadow); }
.order-card-main { display: flex; align-items: center; gap: 10px; }
.order-card-n { font-weight: 700; font-size: 1rem; }
.order-card-client { font-size: 0.9rem; font-weight: 600; color: var(--text); margin-top: 4px; }
.order-card-client .order-card-mail { font-weight: 400; color: var(--muted); font-size: 0.82rem; }
.order-card-meta { color: var(--muted); font-size: 0.85rem; margin-top: 4px; }
.order-badge { font-size: 0.64rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; padding: 2px 8px; border-radius: 999px; }
.order-badge--direct { color: #15803d; background: #dcfce7; }
.order-badge--mixte { color: #b45309; background: #ffedd5; }
.order-badge--expert { color: #b45309; background: #fff7ed; border: 1px solid #f0c98a; }
.piece-card--expert { background: #fff7ed; border-color: #f0c98a; }
.piece-card-desig { font-weight: 600; }
.piece-tag { font-size: 0.64rem; font-weight: 700; text-transform: uppercase; color: #b45309; background: #ffedd5; padding: 1px 7px; border-radius: 999px; margin-left: 6px; }
.piece-card-cotes { color: var(--muted); font-size: 0.82rem; margin-top: 3px; }
.piece-card-prix { font-weight: 700; margin-top: 4px; }
/* Détail des raisons de revue expert dans les résumés admin (commandes / pièces). v2.53. */
.order-card-expert, .piece-card-expert { margin-top: 4px; font-size: 0.78rem; font-weight: 600; color: #b45309; }
.pieces-total { margin-top: 14px; font-size: 1rem; text-align: right; }
.pieces-progress { color: var(--muted); font-size: 0.85rem; }

/* ── Validation des commandes (v1.96) ── */
/* Onglets « À traiter » / « Validés ». (v4.03) HARMONISÉS sur les onglets admin du haut
   (mêmes pilules dans un conteneur carte — l'ancien style souligné détonnait, retour
   utilisateur « formats différents »). */
.orders-tabs { display: inline-flex; gap: 3px; margin-bottom: 14px; background: var(--card); border: 1px solid var(--border); border-radius: var(--r); padding: 4px; box-shadow: var(--shadow-sm); }
.orders-tab { font: inherit; font-size: 0.9rem; font-weight: 600; color: var(--muted); background: transparent; border: none; border-radius: var(--r-sm); padding: 9px 14px; cursor: pointer; }
.orders-tab:hover { background: var(--surface-2); color: var(--text); }
.orders-tab.is-active { background: var(--accent); color: #fff; }
.order-badge--ok { color: #15803d; background: #dcfce7; }
/* (v4.62) Devis SOUMIS par le client, en attente de traitement UNIVEIRA (≠ validé) — ambre
   d'attention du site (cf. .dim-config-hint). */
.order-badge--pending { color: #92510a; background: #fdf1de; border: 1px solid #eebc6d; }
.order-card--ok { border-left: 3px solid #22c55e; }
/* (v5.24) Rangée carte + ACTIONS (Archiver / Désarchiver / Supprimer) : les actions vivent HORS
   du <button> carte (pas de boutons imbriqués), en colonne à droite, centrées verticalement. La
   marge basse de rangée = celle de la carte (10px), reprise sur la colonne d'actions. */
.order-row { display: flex; align-items: stretch; gap: 8px; }
.order-row > .order-card { flex: 1 1 auto; min-width: 0; }
.order-actions { flex: 0 0 auto; display: flex; flex-direction: column; gap: 6px; justify-content: center; margin-bottom: 10px; }
.order-act { font: inherit; font-size: 0.78rem; font-weight: 600; padding: 5px 11px; min-width: 96px;
  border: 1px solid var(--border); border-radius: var(--r-sm); background: #fff; color: var(--text);
  cursor: pointer; transition: background 0.12s, border-color 0.12s, color 0.12s; }
.order-act:hover { background: var(--accent-soft); border-color: var(--accent-line); }
/* Suppression = action DESTRUCTRICE : rouge, hover plein. */
.order-act--del { border-color: #fca5a5; color: #b91c1c; }
.order-act--del:hover { background: #b91c1c; border-color: #b91c1c; color: #fff; }

/* Barre du détail : retour à gauche, boutons de validation à droite. */
.detail-bar { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 10px; margin-bottom: 10px; }
.detail-bar[hidden] { display: none; }   /* v2.42 : la barre ne fuit plus dans les scènes liste/pièces (display:flex écrasait [hidden]) */
.detail-bar .btn-back { margin-bottom: 0; }
.detail-bar-actions { display: flex; gap: 8px; }
/* (v4.65) L'attribut [hidden] doit BATTRE `display: flex` : les actions vivent désormais dans le
   fil d'Ariane (toujours affiché), leur masquage ne dépend plus d'un parent #detailBar[hidden]. */
.detail-bar-actions[hidden] { display: none; }
/* (v4.65) Boutons de validation TOUJOURS à droite : dans le fil d'Ariane (barre haute) comme dans
   la barre BASSE — même quand le « ← Retour » de repli est masqué en admin (sinon l'unique enfant
   partait à gauche avec justify-content: space-between). */
.detail-bar .detail-bar-actions { margin-left: auto; }
/* (v4.65) La barre de détail HAUTE ne porte plus que le retour de repli (masqué en admin) → vide :
   on la masque en vue admin (le fil d'Ariane porte retour + actions). */
body.view-admin #detailBar { display: none; }

/* Boutons de validation (v5.10 : embellis — relief léger, focus visible, état « validé » net,
   micro-enfoncement au clic ; le bouton du DEVIS entier reste le PLEIN vert = action primaire). */
.btn-validate, .piece-validate {
  font: inherit; font-size: 0.85rem; font-weight: 600; letter-spacing: 0.01em;
  padding: 8px 16px; border-radius: 8px;
  border: 1px solid #16a34a; background: #fff; color: #15803d; cursor: pointer;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06);
  transition: background 0.12s, border-color 0.12s, color 0.12s, box-shadow 0.12s, transform 0.05s;
}
.btn-validate:hover, .piece-validate:hover { background: #f0fdf4; border-color: #15803d; box-shadow: 0 2px 6px rgba(22, 163, 74, 0.2); }
.btn-validate:active, .piece-validate:active { transform: translateY(1px); box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06); }
.btn-validate:focus-visible, .piece-validate:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(22, 163, 74, 0.25); }
/* État « validé » : rempli doux + bordure claire (se relit comme un ACQUIS, pas comme une action). */
.btn-validate.is-on, .piece-validate.is-on { background: #dcfce7; border-color: #86efac; color: #166534; box-shadow: none; }
/* Action PRIMAIRE (tout le devis) : plein vert. */
.btn-validate--order { background: #16a34a; border-color: #16a34a; color: #fff; }
.btn-validate--order:hover { background: #15803d; border-color: #15803d; color: #fff; }

/* Menu des pièces : carte + bouton « Valider » côte à côte.
   (v4.05) Le bouton ne s'ÉTIRE plus sur toute la hauteur de la carte (dalle verte de
   ~104 px, retour utilisateur « moche ») : bouton COMPACT, centré verticalement, au
   format standard .btn-validate (mêmes taille/padding/arrondi/états). */
.piece-row { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.piece-row .piece-card { flex: 1; margin-bottom: 0; }
/* (v5.10) Le VISUEL de .piece-validate vient de la règle partagée « Boutons de validation »
   ci-dessus (même gabarit partout) — ne reste ici que sa place dans la rangée. */
.piece-validate { flex: 0 0 auto; align-self: center; min-width: 96px; }
.piece-row--ok .piece-card { border-left: 3px solid #22c55e; }
.pieces-actions { margin-top: 16px; text-align: right; }
.pieces-actions .btn-validate--order { font-size: 0.95rem; padding: 10px 20px; }
.recap-row { display: flex; justify-content: space-between; align-items: baseline; gap: 18px; padding: 9px 2px; border-bottom: 1px solid var(--line); font-size: 0.9rem; }
.recap-row:first-child { border-top: 1px solid var(--line); }
.recap-k { color: var(--muted); white-space: nowrap; }
.recap-v { color: var(--text); font-weight: 500; text-align: right; font-variant-numeric: tabular-nums; }
.recap-v b { font-weight: 700; }
.recap-low { color: var(--err); font-weight: 600; font-size: 0.82rem; margin-left: 6px; }
.recap-feas { display: inline-flex; align-items: center; gap: 6px; font-weight: 600; }
.recap-feas::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex: none; }
.recap-feas.is-ok { color: var(--ok); }
.recap-feas.is-no { color: var(--warn); }
/* En vue client, le bandeau prix devient un PIED de fiche (séparé du récap), Total à droite. */
body.view-client .devis-banner { border-top: 1px solid var(--line); padding-top: 16px; }
body.view-client .devis-banner > .field { flex: 0 1 auto; }
body.view-client .devis-banner > .field:last-child { margin-left: auto; text-align: right; }
body.view-client .devis-banner > .field:last-child input { min-width: 130px; }

/* Espaces RÉDUITS entre les champs de la carte Dimensions en vue client (densité), et
   collapse des notes vides (entre B et H notamment) pour ne pas laisser de trous. */
body.view-client #cardDimensions .field { margin-bottom: 8px; }
body.view-client #cardDimensions .field-input-wrap { margin: 0; }

/* ── COTES EN GRILLE COMPACTE (vue client, v3.29 — revue dirigeants ; 3 COLONNES v3.94) ──
   Les champs de cotes se répartissent sur 3 COLONNES : rangée « A · case Ajuster », puis
   B|H|S ALIGNÉS HORIZONTALEMENT dessous (retour utilisateur — la répartition 2 colonnes
   v3.79 laissait S orphelin sous B). Douille : mêmes 3 colonnes (D1|D2|D3, E|H|L1, L2).
   Les éléments pleine largeur (dividers, notes, cases, sections) gardent leur field-full ;
   le module Couple reste seul sur sa ligne. */
/* :not(#optionsGrid) (v3.39) : la grille des OPTIONS, fusionnée dans Dimensions, garde sa mise
   en page à 1 colonne (seule la grille des COTES passe en colonnes). */
body.view-client #cardDimensions .grid:not(#optionsGrid) { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px 12px; }
body.view-client #cardDimensions .grid > .field { margin-bottom: 0; }   /* le gap de la grille suffit */
body.view-client #cardDimensions .grid > .couple-field { grid-column: 1 / -1; }
/* (v3.32→v3.79→v3.94→v3.95) EMBOUTS & CLÉS : rangée « A = [champ] » PLEINE LARGEUR (règle
   des pastilles, généralisée aux deux modes), la case « Ajuster les cotes » (field-full) sur
   SA rangée dessous — IDENTIQUE dans les deux modes : elle ne bouge plus quand on la coche —,
   puis B|H|S de front sur la rangée suivante (flux naturel : les notes intercalées sont
   field-full ou admin-only). */
/* (v3.79) Les mentions de provenance sous B/H/S et le manche (« (suggérée) », « (estimation
   précise · régression…) ») alourdissaient la colonne (retour utilisateur) : le TEXTE GRIS dit
   déjà « autocomplétée » — masquées en vue CLIENT (l'admin garde toutes ses mentions). */
body.view-client #fieldB .auto-note,
body.view-client #fieldH .auto-note,
body.view-client #fieldS .auto-note,
body.view-client #fieldMancheLargeur .auto-note,
body.view-client #fieldMancheEpaisseur .auto-note { display: none; }
body.view-client #sectionDouille { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px 12px; }   /* (v3.94) lignes de 3 cotes */
/* (v3.95) COTES DE LA DOUILLE en ligne « D1 = [champ court] » (retour utilisateur), comme
   B/H/S : libellé inline + « = », champ 120 px, aide parlante SOUS la ligne (alignée sur le
   champ). Les lignes de 3 restent alignées : libellés à largeur fixe, alignés à droite. */
body.view-client #sectionDouille > .field.douille-cote-edit { display: grid; grid-template-columns: max-content minmax(0, 1fr); column-gap: 8px; row-gap: 2px; align-items: start; justify-items: start; }
/* min-width 34 = largeur de « D1 = » (le ::after compte dans le libellé) : les champs de
   TOUTES les rangées partent du même x, y compris ceux des libellés courts (E, H). */
body.view-client #sectionDouille > .field.douille-cote-edit > label { margin: 0; padding-top: 10px; min-width: 34px; text-align: right; font-weight: 600; }
body.view-client #sectionDouille > .field.douille-cote-edit > label::after { content: " ="; color: var(--muted); font-weight: 400; }
body.view-client #sectionDouille > .field.douille-cote-edit > :not(label) { grid-column: 2; }
/* Champ COMPRESSIBLE (cf. B/H/S plus haut) : jamais de repli sous le libellé. */
body.view-client #sectionDouille > .field.douille-cote-edit .input-unit { width: 120px; max-width: 100%; }
body.view-client #sectionDouille > .field.douille-cote-edit .input-unit > input { flex: 1 1 auto; min-width: 0; width: auto; }
/* (v4.04) Les descriptions .field-help sous les cotes douille sont retirées du HTML
   (le schéma + sa légende expliquent ; l'infobulle title reste sur chaque champ). */
/* (v3.95) Le TRAIT du divider « Cotes » de la douille faisait une barre pleine largeur juste
   sous la case « Ajuster les cotes » (retour utilisateur) → retiré (le label suffit). */
body.view-client #douilleCotesDivider { border-top: none; margin-top: 0; padding-top: 6px; }
/* (v3.95) GARDE : la case « Ajuster les cotes » des EMBOUTS/CLÉS (et son invite) ne doit
   jamais fuir sur la catégorie DOUILLE (état hidden hérité d'une config embout précédente —
   la douille a sa propre case #ajusterCotesDouilleWrap). */
body.cat-douille #ajusterCotesWrap,
body.cat-douille #ajusterCotesHint { display: none !important; }
/* (v3.94) MANCHE (clés) : même grille à 3 colonnes — la Longueur (avec ses pastilles) garde
   sa rangée pleine largeur, Largeur|Épaisseur se rangent de front dessous (au lieu de deux
   champs pleine largeur empilés) ; poignée et case Options restent pleine largeur. */
body.view-client #sectionManche { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px 12px; }
body.view-client #sectionManche[hidden] { display: none; }   /* même garde que #sectionDouille (v3.30) */
body.view-client #sectionManche > #fieldPoignee { grid-column: 1 / -1; }
/* (v4.30) Longueur PLEINE LARGEUR panneau FERMÉ (pour porter ses pastilles Largeur/Épaisseur) ;
   en mode « Personnaliser » (manche-perso-on), Longueur | Largeur | Épaisseur sur UNE rangée de 3
   (champs COURTS, à côté les uns des autres — demande utilisateur). */
body.view-client:not(.manche-perso-on) #sectionManche > #fieldMancheLongueur { grid-column: 1 / -1; }
body.view-client #fieldMancheLargeur,
body.view-client #fieldMancheEpaisseur { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
body.view-client #fieldMancheLargeur > label,
body.view-client #fieldMancheEpaisseur > label { flex-basis: 100%; margin: 0; font-weight: 600; }
body.view-client #fieldMancheLargeur .input-unit,
body.view-client #fieldMancheEpaisseur .input-unit { flex: none; width: 92px; }
body.view-client.manche-perso-on #fieldMancheLongueur .input-unit { width: 92px; }   /* même gabarit court dans la rangée */
/* ⚠️ [hidden] doit BATTRE le display:grid ci-dessus (fix v3.30) — sinon la section douille
   (empreinte, A douille, « Douille à choc ») FUYAIT sur les embouts et les clés. */
body.view-client #sectionDouille[hidden] { display: none; }

/* ── COTES AUTOCOMPLÉTÉES : retour au FOND BLANC (v3.41, revue dirigeants — annule l'encadré
   gris v3.32). La distinction auto/manuel redevient celle d'origine : TEXTE gris #888 (posé en
   inline par setAuto) sur champ blanc, texte noir quand la valeur devient manuelle. Seule
   l'UNITÉ (segment « mm » du .input-unit) garde son fond gris (.field-unit, inchangé). */
.field-error:empty { display: none; }
#dimNote:empty, #tuyEstimateNote:empty { display: none; margin: 0; }

/* ── Layout CLIENT (v3.20, revue dirigeants) : Sélection + schéma technique à GAUCHE, Dimensions
   au MILIEU avec la carte Options SOUS elle (déplacée par setView), DEVIS COMPACT à DROITE
   (pile .results-stack) — la pièce, les cotes ET le prix se lisent SANS scroller ; la version
   détaillée du devis reste la bannière du bas de page (#cardDevis). ── */
body.view-client .selection-row { display: none; }                 /* Sélection déplacée à gauche ; schéma général masqué en client */
/* Colonne de droite = devis compact : visible dès que la carte est révélée (sélection complète). */
body.view-client .layout > .results-stack { display: none; }
/* (v3.41) descendant (plus enfant direct) : le devis compact vit dans le conteneur #rightSticky. */
body.view-client .layout > .results-stack:has(#cardDevisCompact:not(.nl-hidden)) { display: flex; flex: 0 0 290px; }
body.view-client .layout > .mid-stack { flex: 1.6 1 0; }
body.view-client .layout > .left-stack { flex: 1.1 1 0; }
body.view-client .schema-stack { position: static; }               /* flux naturel : on rend la main à l'alignement (désactivé) */
body.view-client .schema-col { height: 300px; }                    /* schémas plus compacts dans la colonne */
/* Schéma technique : la cote S est dessinée TOUT EN HAUT du plan → la barre de titre la
   masquait. On réserve de la place sous la barre (padding-top). (v3.34) L'encadré est BIEN
   ROGNÉ : il ÉPOUSE son schéma (hauteur naturelle, plus de cadre étiré ni de min-height). */
body.view-client #schemaTechBox { flex: 0 0 auto; height: auto; min-height: 0; padding-top: 48px; }
body.view-client #cardSelection { margin-bottom: 0; }
body.view-client .layout { gap: 24px; }

/* Carte Options SOUS Dimensions (v3.20) : hauteur naturelle (plus d'étirement de colonne) ; le
   schéma des options est FORTEMENT RÉDUIT (revue dirigeants — il se confondait avec le schéma
   de la pièce configurée, à gauche) : vignette basse, quel que soit le type d'outil. */
/* (v3.39) Schéma des options DANS la carte Dimensions (fusion) : compact, image centrée
   (remplace les anciennes règles flex de la carte #cardOptions, devenue coquille vide). */
body.view-client #cardDimensions #optionsSchemaBox { min-height: 0; height: auto; max-height: 150px; }
body.view-client #cardDimensions #optionsSchemaBox img { max-height: 130px; width: auto; max-width: 100%; margin: 0 auto; }
/* Bouton PLEIN ÉCRAN du schéma des options (v3.41, revue dirigeants) : bien calé dans le COIN
   SUPÉRIEUR DROIT de l'encadré et PLUS VISIBLE (plus grand, couleur accent, ombre). */
#optionsSchemaBox > .schema-fs-btn {
  top: 6px; right: 6px; width: 34px; height: 34px; font-size: 1.14rem;
  color: var(--accent-strong); border-color: var(--accent-line); background: #fff;
  box-shadow: var(--shadow-sm);
}

/* ── FENÊTRE ÉTROITE (vue client, v3.41 — revue dirigeants) : les 3 colonnes s'empilent bien
   AVANT le seuil téléphone (720px) — une fenêtre non maximisée ne doit rien écraser. Les piles
   collantes redeviennent statiques (plus de « côte à côte » à suivre en colonne unique). ── */
@media (max-width: 1100px) {
  body.view-client .layout { flex-direction: column; }
  body.view-client .layout > .left-stack,
  body.view-client .layout > .mid-stack,
  body.view-client .layout > .results-stack { flex: none; width: 100%; }
  body.view-client .left-stack > #schemaCol { position: static; }
  body.view-client .right-sticky { position: static; }
}
/* Cotes en UNE COLONNE quand les 2 colonnes déborderaient (téléphone / fenêtre très étroite) :
   A, B, H, S — et les cotes de douille — s'empilent VERTICALEMENT (quitte à devoir défiler). */
@media (max-width: 560px) {
  body.view-client #cardDimensions .grid:not(#optionsGrid) { grid-template-columns: 1fr; }
  body.view-client #fieldB,
  body.view-client #fieldH,
  body.view-client #fieldS { grid-column: auto; }   /* (v3.95) #fieldA reste 1/-1 (= pareil en 1 colonne) */
  body.view-client #sectionDouille { grid-template-columns: 1fr; }
  body.view-client #sectionManche { grid-template-columns: 1fr; }      /* (v3.94) manche empilé aussi */
}

/* ── Colonnes (vue client) : les piles s'étirent (bornes du sticky), mais les ENCADRÉS de
   schéma restent ROGNÉS à leur contenu (v3.34 — plus de « bas alignés » v2.01 pour le schéma). */
body.view-client .layout { align-items: stretch; }
body.view-client .layout > .left-stack,
body.view-client .layout > .mid-stack,
body.view-client .layout > .results-stack { align-self: stretch; }

/* ── PILE DE SCHÉMAS COLLANTE (v3.34, revue dirigeants — remplace le sticky d'IMAGE v3.31) :
   les encadrés (schéma technique + plan réalisé + bouton Annuler) ÉPOUSENT leur contenu et
   c'est LA PILE ENTIÈRE qui suit le défilement (position:sticky à 16 px du haut de fenêtre,
   comme le devis compact), bornée par la hauteur de la colonne de gauche (elle s'arrête au
   bas de la rangée). Inerte quand la colonne n'est pas plus haute que la pile (mobile). ── */
body.view-client .left-stack > #schemaCol { flex: 0 0 auto; position: sticky; top: 16px; }
/* Le SCHÉMA remplit la largeur de l'encadré, l'encadré prend la hauteur de l'image (patron
   clés v3.25 généralisé à TOUS les types — embouts compris). */
body.view-client #schemaTechBox > #imageSchema { max-width: 96%; max-height: none; width: 96%; height: auto; margin: 0 auto; }
body.view-client .mid-stack > #cardDimensions { flex: 0 0 auto; }                        /* Dimensions : hauteur naturelle (Options dessous) */
/* Écart uniforme (quelques px) entre TOUTES les bannières (bas alignés) et la bannière Devis. */
body.view-client #cardDevis { margin-top: 24px; }

/* ── DEVIS COMPACT (colonne de droite, vue client — v3.20) ──
   (v3.41) Le sticky passe au CONTENEUR .right-sticky (devis compact + plan réalisé) : les deux
   défilent ENSEMBLE — plus de chevauchement du plan par le devis collant. En admin le conteneur
   est transparent (display:contents — le devis compact y est de toute façon masqué). */
.right-sticky { display: contents; }
body.view-client .right-sticky {
  display: flex; flex-direction: column; gap: 20px;
  position: sticky; top: 16px;
}
#cardDevisCompact .dc-price { margin: 2px 0 12px; }
#cardDevisCompact .dc-row { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; padding: 5px 0; }
#cardDevisCompact .dc-label { color: var(--muted); font-size: 0.85rem; }
#cardDevisCompact .dc-value { font-weight: 600; font-variant-numeric: tabular-nums; }
#cardDevisCompact .dc-row--total { border-top: 1px solid var(--line); }
#cardDevisCompact .dc-value--total { font-size: 1.25rem; font-weight: 700; color: var(--accent-strong); }
/* CTA empilés dans la colonne étroite (le bloc .devis-actions est déplacé ici par setView). */
#cardDevisCompact .devis-cta-row { flex-direction: column; gap: 8px; }
#cardDevisCompact .hero-cta { width: 100%; }
#cardDevisCompact .dc-hint { font-size: 0.78rem; color: var(--muted); margin: 12px 0 10px; }
/* Variante du bouton d'aide POSÉE SUR CARTE BLANCHE (le .help-btn d'origine est prévu pour
   l'en-tête sombre : texte blanc, fond translucide → invisible ici). */
#cardDevisCompact .help-btn--devis {
  width: 100%;
  border-color: var(--border); background: var(--surface-2); color: var(--accent-strong);
}
#cardDevisCompact .help-btn--devis:hover { background: var(--accent-soft); }

/* Mention « (suggérée) » dans les champs B/H/S autocomplétés (vue client + Personnaliser). */
.field-input-wrap { position: relative; display: block; }
/* Mention « (suggérée) » : en FLUX, sous le champ (dans .cote-col), masquée par défaut. */
.suggested-tag { display: none; margin-top: 3px; padding-left: 2px; font-size: 0.72rem; font-style: italic; color: #9a9a9a; pointer-events: none; }
body.view-client.perso-on .cote-col.is-suggested .suggested-tag { display: block; }

/* Prix du brut + menu des grilles de prix acier Excel (vue admin, v1.32) */
.brut-price-row { display: flex; align-items: center; gap: 6px; }
.brut-price-row > #coutMatiere { flex: 1; min-width: 0; }
.brut-grilles { position: relative; flex: 0 0 auto; }
/* (v5.11) Harmonisé avec ses voisins (.btn-mini « Voir prix acier ») : bordure 1px de charte
   (la 0.5px sous-pixel rendait inégalement), rayon standard, couleurs de variables. */
.brut-grilles-toggle {
  border: 1px solid var(--border); background: #fff; cursor: pointer;
  border-radius: var(--r-sm); padding: 5px 9px; line-height: 1; color: var(--text); font-size: 0.82rem;
}
.brut-grilles-toggle:hover { background: var(--accent-soft); border-color: var(--accent-line); }
.brut-grilles-menu {
  position: absolute; right: 0; top: calc(100% + 4px); z-index: 30;
  background: #fff; border: 0.5px solid #c8ccd2; border-radius: 6px;
  box-shadow: 0 4px 16px rgba(0,0,0,0.14); min-width: 240px; padding: 4px;
}
.brut-grilles-menu[hidden] { display: none; }
.brut-grilles-titre { font-size: 0.72rem; color: var(--muted); padding: 5px 8px 3px; }
.brut-grilles-menu a {
  display: block; padding: 6px 8px; border-radius: 4px;
  color: #1a4d8f; text-decoration: none; font-size: 0.82rem;
}
.brut-grilles-menu a::after { content: " · .xlsx"; color: var(--muted); font-size: 0.72rem; }
.brut-grilles-menu a:hover { background: #eef3fb; }

/* Valeur NÉGATIVE saisie (interdite, v1.32) : rouge + liseré, et calculs bloqués. */
.input-negative {
  color: #dc2626 !important;
  border-color: #dc2626 !important;
  box-shadow: 0 0 0 1px #dc2626;
}

/* ── DEVIS — détail dépliable du temps de Programmation / réglage (v1.31) ──────── */
.prog-detail-toggle {
  margin-left: 6px; padding: 0 3px; border: none; background: none; cursor: pointer;
  color: var(--muted); font-size: 0.78rem; line-height: 1;
}
.prog-detail-toggle:hover { color: var(--text, #111); }
.prog-detail {
  margin: 2px 0 6px 14px; padding: 6px 10px;
  background: #f6f8fa; border-left: 2px solid #d8dee4; border-radius: 0 4px 4px 0;
  font-size: 0.78rem;
}
.prog-detail-line { display: flex; justify-content: space-between; gap: 18px; color: var(--muted); padding: 1px 0; }
.prog-detail-line.prog-detail-total { font-weight: 500; color: #111; border-top: 1px solid #d8dee4; margin-top: 3px; padding-top: 3px; }
/* Ligne de FORMULE en tête d'un détail dépliable (jet d'eau, v1.78). */
.prog-detail-formula { color: var(--accent-strong, #01395f); font-style: italic; margin-bottom: 4px; }
/* Formule de découpe jet d'eau ÉDITABLE en place dans le détail du devis (v1.79). */
.jet-formula-row { display: flex; align-items: center; gap: 8px; font-weight: 600; color: var(--text, #111); margin-bottom: 3px; }
.jet-formula-lab { flex: 0 0 auto; white-space: nowrap; }   /* « coût (€) = » sur une seule ligne, aligné avec le champ */
.jet-formula-row .jet-formula-input { flex: 1 1 auto; min-width: 100px; }
.jet-formula-hint { color: var(--muted); font-size: 0.72rem; margin-bottom: 5px; }
.jet-detail-calc { display: block; }
/* Coefficients ÉDITABLES du temps d'étude (panneau « Étude », v1.80). */
.etude-coefs { margin-bottom: 6px; padding-bottom: 5px; border-bottom: 1px dashed #d8dee4; }
/* Libellé à gauche, groupe « valeur + unité » à droite. Le groupe valeur reste INSÉCABLE
   (l'unité ne passe jamais SOUS le champ) ; seul le retour label↔valeur est autorisé. */
.etude-coef-line { display: flex; align-items: baseline; justify-content: space-between; flex-wrap: wrap; gap: 4px 10px; color: var(--text, #111); padding: 2px 0; }
.coef-lab { flex: 1 1 auto; min-width: 0; }
.coef-val { flex: 0 0 auto; display: inline-flex; align-items: baseline; gap: 3px; white-space: nowrap; color: var(--muted); }
.etude-coef { width: 46px; padding: 1px 5px; font-size: 0.78rem; text-align: right; border: 1px solid var(--border); border-radius: var(--r-sm); font-variant-numeric: tabular-nums; }
.etude-coef:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
/* Lien « Voir prix acier » dans la ligne Matière du devis (v1.79). */
.devis-prix-link { border: none; background: none; padding: 0; cursor: pointer; font: inherit; font-size: inherit; color: var(--accent, #15567f); white-space: nowrap; }
.devis-prix-link:hover { color: var(--accent-strong, #01395f); text-decoration: underline; }

/* ── Bannière-résumé NON ÉDITABLE de la sélection (vue admin, onglets Fabrication & Devis, v1.88) ──
   Remplace les sélecteurs éditables par une phrase ; en Devis, ajoute cotes + options en petit. */
.selection-summary { display: none; }
body.view-admin.atab-piece #selectionSummary,
body.view-admin.atab-fabrication #selectionSummary,
body.view-admin.atab-devis #selectionSummary {
  display: block; margin: 0 0 18px; padding: 12px 18px;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--r); box-shadow: var(--shadow-sm);
}
.selection-summary-text { font-size: 0.95rem; font-weight: 650; color: var(--accent-strong); }
/* Détail cotes + options (en petit) : désormais dans Pièce, Fabrication ET Devis (v1.89). */
.selection-summary-detail { display: none; }
body.view-admin.atab-piece #selectionSummaryDetail,
body.view-admin.atab-fabrication #selectionSummaryDetail,
body.view-admin.atab-devis #selectionSummaryDetail {
  display: block; margin-top: 5px; font-size: 0.78rem; color: var(--muted); line-height: 1.45;
}
/* Sélecteurs éditables (menus déroulants) MASQUÉS en vue admin : la bannière Sélection n'affiche que la
   PHRASE-résumé (#selectionSummary), comme dans Fabrication/Devis (v1.95 — une commande définit déjà la
   pièce, l'admin ne re-sélectionne pas le type/attachement). EXCEPTION : mode « test » (admin-config) où
   on rouvre les menus pour configurer une pièce d'essai. */
body.view-admin.atab-piece:not(.admin-config) .selection-row,
body.view-admin.atab-fabrication .selection-row,
body.view-admin.atab-devis .selection-row { display: none; }

/* ── TUYAUTER — panneau ADMIN à deux colonnes (v1.24) ───────────────────────────
   En vue admin, les cotes B/H/S/P du tuyauter sont présentées côte à côte :
   colonne VALIDÉ par le client (lecture seule + provenance « Saisie manuelle » /
   « Autocomplétée par les cotes de la pièce UNT… ») | colonne À AJUSTER par l'admin
   (champ VIDE par défaut, suggestion par calcul en filigrane + formule si « Voir les
   formules » est cochée). Les champs B/H/S/P standards sont masqués (le panneau les
   remplace) ; A reste visible. */
body.view-admin.tuy-split #fieldB,
body.view-admin.tuy-split #fieldH,
body.view-admin.tuy-split #fieldS,
body.view-admin.tuy-split #fieldP { display: none !important; }

.tuy-admin-panel { font-style: normal; color: var(--text, #111); }
/* (v5.15) La phrase d'en-tête (« Ouverture demandée A = … ») est RETIRÉE de l'affichage
   (demande utilisateur) — l'info vit déjà dans les notes de provenance sous les champs. */
.tuy-admin-panel .tuy-head { display: none; }
/* (v5.15) Colonnes ALIGNÉES sur le gabarit des cotes de l'onglet Outil (82px | 1fr | 1fr,
   gap 22) : les lignes B/H/S/P du tuyauter ne sont plus décalées par rapport à la ligne A.
   Les en-têtes de colonnes internes (doublons de VALEUR CLIENT / AJUSTEMENTS) sont masqués. */
.tuy-admin-grid {
  display: grid;
  grid-template-columns: 82px minmax(0, 1fr) minmax(0, 1fr);
  gap: 7px 22px;
  align-items: start;
}
.tuy-admin-grid > div:first-child, .tuy-col-head { display: none; }
.tuy-cote-label { font-weight: 600; padding-top: 7px; font-size: 0.82rem; justify-self: end; text-align: right; }
.tuy-validated input { width: 100%; background: #eef1f5; color: #111; cursor: default; }
.tuy-admin > input { width: 100%; }
.tuy-admin > input.tuy-invalid { border-color: #dc2626; }
/* (v5.28) Provenance de la rangée MASQUÉE par défaut — dépliée par la flèche ▸ (.tuy-open),
   comme le panneau de provenance des autres cotes (v5.20). */
.tuy-prov { display: none; font-size: 0.72rem; color: var(--muted); margin-top: 3px; line-height: 1.4; }
.tuy-validated.tuy-open .tuy-prov { display: block; }
.tuy-prov strong { font-style: normal; color: #444; }
/* Formule d'estimation ÉDITABLE sous le champ admin : réutilise le style « .formula-edit »
   (visible seulement quand « Voir les formules » est cochée — règles globales). */
.tuy-admin .formula-edit { margin-top: 4px; }
/* Champ texte ÉDITABLE de la formule d'estimation (syntaxe moteur). */
.tuy-formula-input { flex: 1; min-width: 0; }
.tuy-formula-input.tuy-mf-invalid { border-color: #dc2626; }

/* ── « Besoin d'aide ? » : bouton d'en-tête (vue client) + tuile de contact au premier plan (v1.87) ── */
/* (v3.85) Boutons d'en-tête (panier / aide / déconnexion) : marine-sur-clair (bannière claire). */
.help-btn {
  border: 1px solid rgba(0, 57, 95, 0.35); background: #fff; color: var(--brand-navy);
  font: inherit; font-size: 0.85rem; font-weight: 600; padding: 7px 15px; border-radius: 999px; cursor: pointer;
  white-space: nowrap; transition: background .15s;
}
.help-btn:hover { background: var(--accent-soft); }
/* (v4.96) « Vue admin » : bouton d'en-tête DISTINCT (accent marine plein) — action réservée à un
   compte administrateur, à ne pas confondre avec « Voir mon panier »/« Besoin d'aide ? ». */
.btn-admin-view { background: var(--brand-navy); color: #fff; border-color: var(--brand-navy); }
.btn-admin-view:hover { background: #012a48; }
.btn-admin-view[hidden] { display: none; }
.help-modal { position: fixed; inset: 0; z-index: 1000; display: flex; align-items: center; justify-content: center; padding: 20px; }
.help-modal[hidden] { display: none; }
.help-backdrop { position: absolute; inset: 0; background: rgba(1,32,56,.55); backdrop-filter: blur(2px); }
.help-card {
  position: relative; background: #fff; border-radius: 16px; padding: 26px 30px; max-width: 380px; width: 100%;
  box-shadow: 0 24px 70px rgba(1,57,95,.40); animation: helpPop .16s ease-out;
}
@keyframes helpPop { from { opacity: 0; transform: translateY(8px) scale(.98); } to { opacity: 1; transform: none; } }
.help-card h3 { margin: 0 0 6px; color: var(--brand-navy); font-size: 1.14rem; }
.help-card > p { margin: 0; color: var(--text); font-size: 0.9rem; line-height: 1.5; }
.help-close {
  position: absolute; top: 8px; right: 12px; border: none; background: none; font-size: 1.5rem;
  line-height: 1; color: var(--muted); cursor: pointer; padding: 4px;
}
.help-close:hover { color: var(--brand-navy); }
.help-contact { list-style: none; margin: 18px 0 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.help-contact li { display: flex; align-items: center; gap: 11px; font-size: 1rem; }
.help-ico { font-size: 1.14rem; }
.help-contact a { color: var(--brand-navy); font-weight: 700; text-decoration: none; }
.help-contact a:hover { text-decoration: underline; }
@media print { .help-btn, .help-modal { display: none !important; } }

/* ── « VOIR MON PANIER » (v3.65, PAGE PLEINE v3.66) : badge du bouton d'en-tête + page #cartPage ── */
.cart-btn-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; height: 18px; margin-left: 7px; padding: 0 5px;
  border-radius: 999px; background: var(--brand-navy); color: #fff;   /* (v3.85) inversé (bouton clair) */
  font-size: 0.72rem; font-weight: 800; line-height: 1;
}
.cart-btn-count[hidden] { display: none; }
/* La PAGE panier remplace le configurateur (body.client-cart-view) — mise en page cohérente :
   mêmes cartes blanches (.card) que le reste du site, bouton retour des scènes (.btn-back). */
.client-cart-page { display: none; }
body.view-client.client-cart-view #cartPage { display: block; }
body.view-client.client-cart-view #categoryPicker,
body.view-client.client-cart-view #typePicker,
body.view-client.client-cart-view .layout,
body.view-client.client-cart-view #cardDevis,
body.view-client.client-cart-view #clientSteps { display: none !important; }
.client-cart-page .card { margin-bottom: 18px; }
/* (v4.32) Page panier — vue intermédiaire : « Ajouter un outil » (secondaire) + « Obtenir mon
   devis » (primaire) côte à côte. */
.cart-page-actions { margin-top: 14px; display: flex; flex-wrap: wrap; gap: 12px; }
.cart-add-tool { display: inline-flex; align-items: center; gap: 6px; font: inherit; font-weight: 600; cursor: pointer;
  padding: 12px 20px; border-radius: var(--r-sm); border: 1px solid var(--accent-line); background: var(--accent-soft);
  color: var(--brand-navy); transition: background 0.15s, border-color 0.15s; }
.cart-add-tool:hover { background: var(--accent); border-color: var(--accent); color: #fff; }
.cart-add-tool .cat-plus { font-size: 1.15em; line-height: 1; font-weight: 700; }
.cart-modal-empty { margin: 8px 0 0; color: var(--muted); font-size: 0.9rem; }
.cart-columns--modal { margin: 12px 0 0; }
.cart-modal-total { margin: 10px 0 0; font-size: 0.9rem; color: var(--text); }
/* Devis obtenus : lignes DÉPLIABLES (bouton pleine largeur, flèche pivotante) → panneau
   listant les pièces du devis + statuts de validation. */
.cart-order-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px;
  width: 100%; text-align: left; font: inherit; cursor: pointer;
  padding: 10px 12px; margin-top: 8px; background: var(--surface-2);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  transition: border-color .12s; }
.cart-order-row:hover { border-color: var(--accent); }
.cart-order-caret { color: var(--muted); font-size: 0.82rem; transition: transform .15s; }
.cart-order-row.is-open .cart-order-caret { transform: rotate(90deg); }
.cart-order-row.is-open { border-radius: var(--r-sm) var(--r-sm) 0 0; }
.cart-order-n { font-weight: 700; font-size: 0.9rem; }
.cart-order-meta { font-size: 0.85rem; color: var(--muted); }
.cart-order-meta b { color: var(--text); }
.cart-order-pieces { border: 1px solid var(--border); border-top: none;
  border-radius: 0 0 var(--r-sm) var(--r-sm); padding: 4px 12px 10px; background: #fff; }
.cart-order-pieces[hidden] { display: none; }
.cart-order-piece { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px;
  padding: 8px 0; border-bottom: 1px solid var(--line); font-size: 0.9rem; }
.cart-order-piece:last-child { border-bottom: none; }
.cop-desig { font-weight: 600; }
.cop-cotes, .cop-ref { color: var(--muted); font-size: 0.82rem; }
.cop-prix { margin-left: auto; font-weight: 700; white-space: nowrap; }
/* (v3.68) Réimpression du devis — couleurs : SYSTÈME DE BOUTONS niveau 2 (fin de feuille, v3.91). */
.cart-order-actions { padding-top: 10px; margin-top: 4px; border-top: 1px solid var(--line); }
.cart-order-print { font: inherit; font-size: 0.85rem; padding: 7px 14px; }

/* ── Modale COORDONNÉES (v2.03) ── */
.contact-card { max-width: 420px; }
.contact-req { color: var(--muted); font-size: 0.78rem; }
.contact-form { display: flex; flex-direction: column; gap: 12px; margin: 18px 0 4px; }
.contact-field { display: flex; flex-direction: column; gap: 4px; }
.contact-field > span { font-size: 0.82rem; font-weight: 600; color: var(--text); }
.contact-field > input { font: inherit; font-size: 0.9rem; padding: 9px 11px; border: 1px solid var(--border); border-radius: var(--r-sm); background: #fff; color: var(--text); }
.contact-field > input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.contact-hint { margin: 8px 0 0; color: var(--err); font-size: 0.82rem; }
.contact-field > input.contact-invalid { border-color: var(--err); box-shadow: 0 0 0 2px rgba(220,38,38,0.12); }
.contact-actions { display: flex; flex-direction: column; gap: 10px; margin-top: 16px; }
.contact-actions .hero-cta { width: 100%; }

/* ── (v4.82) Éditeur des temps d'usinage fixes par forme d'attachement personnalisé (Gamme & taux) ── */
.fab-perso { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--border); }
.fab-perso h3 { margin: 0 0 4px; font-size: 0.95rem; color: var(--accent-strong); }
.fab-perso-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 10px; margin-top: 10px; }
.fab-perso-field { display: flex; flex-direction: column; gap: 4px; font-size: 0.85rem; }
.fab-perso-field > span:first-child { font-weight: 600; color: var(--text); }
.fab-perso-field .input-unit { width: 100%; }

/* ── (v4.75) VERROU DE PRIX — compte + coordonnées obligatoires pour voir les prix ──
   PROTOTYPE : comptes en localStorage (sans sécurité). body.price-locked masque toute valeur
   de prix et met le bouton-verrou à sa place. Déverrouillage = connexion + confirmation des
   coordonnées (1 fois par session), piloté par app.js. Le masquage est COSMÉTIQUE. */
/* Scopé à la vue CLIENT : l'admin (view-admin, même app.js/CSS) voit toujours les prix. */
.price-gate-btn { display: none; width: 100%; }
body.view-client.price-locked .price-gate-btn { display: inline-flex; }
body.view-client.price-locked #dcUnitRow,
body.view-client.price-locked #dcIndicative,
body.view-client.price-locked #devisDegressif,
body.view-client.price-locked #cardDevis .field:has(.price-badge) { display: none !important; }

/* ── (v4.75) Modale de COMPTE (connexion / création) : réutilise .contact-* ── */
.auth-signup-fields { display: contents; }          /* les champs s'insèrent dans le flux .contact-form */
/* (v4.93) `display:contents` écrase le display:none apporté par [hidden] → en mode CONNEXION les
   champs d'inscription restaient visibles. On restaure le masquage. */
.auth-signup-fields[hidden] { display: none; }
.auth-switch { margin: 14px 0 0; font-size: 0.85rem; color: var(--muted); text-align: center; }
.link-btn { background: none; border: 0; padding: 0; font: inherit; font-weight: 600;
  color: var(--accent-strong); cursor: pointer; text-decoration: underline; }
.link-btn:hover { color: var(--accent); }

/* ── (v4.75) Bandeau de statut du compte (client) : e-mail connecté + déconnexion ── */
.client-auth-bar { display: flex; align-items: center; gap: 8px; justify-content: flex-end;
  padding: 6px 18px; font-size: 0.82rem; color: var(--muted);
  background: var(--surface-2); border-bottom: 1px solid var(--border); }
.client-auth-bar[hidden] { display: none; }
.client-auth-bar svg { color: var(--accent-strong); flex: none; }
.client-auth-bar .cab-user { font-weight: 600; color: var(--text); }
.client-auth-bar .link-btn { margin-left: 4px; }

/* ── (v4.83) Badge de COMPTE cliquable dans l'en-tête (nom / « Se connecter ») + tuile « Mon compte » ── */
.role-badge--account { cursor: pointer; border-radius: 999px; padding: 3px 10px; transition: background .15s ease; }
.role-badge--account:hover,
.role-badge--account:focus-visible { background: rgba(255, 255, 255, 0.16); outline: none; }
.account-info { display: grid; gap: 10px; margin: 16px 0 4px; }
.account-row { display: flex; justify-content: space-between; gap: 16px; font-size: 0.9rem;
  border-bottom: 1px solid var(--border); padding-bottom: 8px; }
.account-row dt { margin: 0; color: var(--muted); font-weight: 600; }
.account-row dd { margin: 0; color: var(--text); text-align: right; word-break: break-word; }

/* ── (v4.84) Scène « Comptes clients » : cartes de comptes + entête d'un compte ── */
.account-card { display: flex; align-items: center; gap: 14px; width: 100%; text-align: left;
  background: #fff; border: 1px solid var(--border); border-radius: var(--r-md); padding: 14px 16px;
  margin-bottom: 10px; cursor: pointer; font: inherit; transition: border-color .15s, box-shadow .15s; }
.account-card:hover { border-color: var(--accent); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); }
.account-card .ac-avatar { flex: none; width: 38px; height: 38px; border-radius: 50%;
  background: var(--accent-soft); color: var(--accent-strong); display: grid; place-items: center; font-weight: 700; }
.account-card .ac-main { flex: 1 1 auto; min-width: 0; }
.account-card .ac-name { font-weight: 600; color: var(--text); }
/* (v4.98) Pastille « admin » à côté du nom dans la liste des comptes (vue admin). */
.account-card .ac-role { display: inline-block; margin-left: 8px; padding: 1px 8px; border-radius: 999px;
  background: var(--brand-navy); color: #fff; font-size: 0.68rem; font-weight: 700; letter-spacing: .03em;
  text-transform: uppercase; vertical-align: 1px; }
.account-card .ac-sub { font-size: 0.85rem; color: var(--muted); word-break: break-word; }
.account-card .ac-count { flex: none; font-size: 0.85rem; color: var(--accent-strong); font-weight: 600; white-space: nowrap; }
.accounts-account-head { margin: 0 0 14px; padding: 12px 16px; background: var(--surface-2);
  border: 1px solid var(--border); border-radius: var(--r-md); }
.accounts-account-head .aah-name { font-weight: 700; color: var(--text); }
.accounts-account-head .aah-sub { font-size: 0.85rem; color: var(--muted); margin-top: 2px; word-break: break-word; }
/* Bouton « Coordonnées du client » dans la sidebar « Devis en cours ».
   (v5.08) Son gabarit (police 0.9rem/600, padding, gap, hover) vient de la règle PARTAGÉE des
   éléments de sidebar (bloc .asb-item, …, .asb-coords) — il avait sa propre police (font:inherit
   → 1rem) qui détonnait. Ne reste ici que son écart au groupe. */
.asb-coords { margin-bottom: 2px; }

/* ── Lisibilité des tableaux Catalogue & Prix acier (v1.89) : un FOND GRIS derrière les CASES
   BLANCHES → les cellules ressortent ; en-têtes/intitulés en gris un peu plus soutenu. ── */
.cat-table-wrap { background: var(--surface-2); }
.cat-table th { background: var(--readonly-bg); }
.cat-table tbody tr:nth-of-type(even):not(.cat-detail) { background: rgba(1, 57, 95, .035); }
.cat-table tbody tr:hover { background: var(--accent-soft); }
.prix-table { background: var(--surface-2); }
.prix-table thead th, .prix-table .prix-rowhead, .prix-table .prix-corner { background: var(--readonly-bg) !important; }

/* ══ SYSTÈME DE BOUTONS (v3.91, revue design « pro » — point 1) : 3 NIVEAUX ══
   1. PRIMAIRE — .hero-cta (+ .login-btn--client) : dégradé de marque, texte blanc. Réservé à
      L'ACTION PRINCIPALE de l'écran (Valider mon outil / Obtenir mon devis / connexion).
      Variante sémantique : .hero-cta--validate (ambre — revue expert).
   2. SECONDAIRE — contour bleu acier (--accent-line), fond bleu très clair (--accent-soft),
      texte marine ; survol = accent PLEIN + blanc. Les COULEURS vivent dans la règle groupée
      ci-dessous (source unique — les règles locales ne gardent que leurs TAILLES).
      Variantes calmes (survol doux, contextes denses) : .btn-mini (admin), .help-btn (bandeau).
   3. TERTIAIRE — texte seul, sans fond : .picker-back, .btn-back, .cat-expand.
   Exception SÉMANTIQUE : .btn-validate (VERT — valider un devis/une pièce, admin).
   ⚠️ Placé en FIN DE FEUILLE : les `font: inherit` des règles locales réinitialisent
   font-weight — la position garantit que la graisse 600 du niveau 2 reprenne la main. */
.login-btn--admin, .prix-import-label, .cart-order-print, #btnPrixAddTable,
#catalogueActions button, #catalogueActions .prix-import-label,
#prixAcierActions button, #prixAcierActions .prix-import-label {
  border: 1px solid var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent-strong);
  font-weight: 600;
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.login-btn--admin:hover, .prix-import-label:hover, .cart-order-print:hover, #btnPrixAddTable:hover,
#catalogueActions button:hover, #catalogueActions .prix-import-label:hover,
#prixAcierActions button:hover, #prixAcierActions .prix-import-label:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
/* (v4.12) Bouton « Annuler la dernière action » DÉSACTIVÉ (rien à annuler) : estompé, pas de
   survol plein. (Placé après le système de boutons pour battre le :hover niveau 2.) */
#catalogueActions .btn-undo:disabled, #prixAcierActions .btn-undo:disabled { opacity: .45; cursor: not-allowed; }
#catalogueActions .btn-undo:disabled:hover, #prixAcierActions .btn-undo:disabled:hover {
  background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-strong);
}
/* Confirmation de suppression d'un tableau de prix (2e clic requis) : rouge affirmé. */
.prix-del-table--confirm, .prix-del-table--confirm:hover { color: #fff; background: var(--err); border-color: var(--err); font-weight: 600; }

/* ══ (v5.31) COMPTES SUR VALIDATION UNIVEIRA ═══════════════════════════════════
   Mur de connexion (espace client, mode serveur) + tuile de demande compacte +
   boutons des demandes de compte (vue admin). */

/* MUR DE CONNEXION (v5.35 — refonte demandée : tuile INTÉGRÉE À LA PAGE, plus d'overlay
   ni de flou). Sans compte validé (mode serveur, espace client) : le menu de sélection et
   tout le contenu sont RETIRÉS (display:none) et la tuile de connexion/demande devient un
   ÉLÉMENT de la page, centrée sous l'en-tête. Elle reste non fermable (croix masquée,
   Échap/backdrop ignorés — closeAuth garde le mur). Hors mur, la tuile redevient la
   modale flottante habituelle (aucune de ces règles ne s'applique).
   ⚠️ Dépend du REPARENTAGE v5.34 (#authModal enfant direct de <body>) : sinon la tuile,
   située dans .container, serait masquée avec lui. */
body.auth-wall .container, body.auth-wall .client-steps { display: none; }
body.auth-wall #authClose { display: none; }
body.auth-wall #authModal { position: static; inset: auto; z-index: auto; padding: 44px 20px 64px; }
body.auth-wall #authModal .help-backdrop { display: none; }
body.auth-wall #authModal .help-card {
  animation: none;
  border: 1px solid var(--line);
  box-shadow: 0 12px 36px rgba(1,57,95,.14);
}

/* Tuile de connexion / demande : hauteur PLAFONNÉE + DÉFILANTE (retour utilisateur :
   la tuile débordait sans scroll). En mode demande : champs sur 2 colonnes. */
#authModal .help-card { max-height: min(92vh, 700px); overflow-y: auto; }
#authModal.auth-modal--signup #authSignupFields {
  display: grid; grid-template-columns: 1fr 1fr; gap: 2px 14px;
}
#authModal.auth-modal--signup #authSignupFields .contact-field:nth-child(3) { grid-column: 1 / -1; }   /* Entreprise pleine largeur */

/* Demandes de compte (vue admin) : carte compte + actions Valider / Refuser. */
.account-card.req-card { cursor: default; }
.req-actions { display: flex; gap: 8px; align-items: center; margin-left: auto; flex: 0 0 auto; }
.req-act {
  border: 1px solid var(--border); background: #fff; border-radius: var(--r-sm);
  padding: 6px 12px; font-size: 0.82rem; font-weight: 600; cursor: pointer; white-space: nowrap;
}
.req-act--ok { border-color: #15803d; color: #15803d; }
.req-act--ok:hover { background: #f0fdf4; }
.req-act--no { border-color: var(--err); color: var(--err); }
.req-act--no:hover { background: #fdecec; }

/* ── (v5.33) JOURNAL D'ACTIVITÉ (vue admin) : barre de filtre + tableau des traces. ── */
.journal-bar { display: flex; align-items: center; gap: 12px; margin: 0 0 12px; }
.journal-bar input[type=search] { flex: 1 1 auto; max-width: 420px; }
.journal-count { color: var(--muted); font-size: 0.82rem; white-space: nowrap; }
.journal-table { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
.journal-table th {
  text-align: left; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--muted); padding: 6px 10px; border-bottom: 2px solid var(--border);
}
.journal-table td { padding: 7px 10px; border-bottom: 1px solid var(--line); vertical-align: top; }
.journal-table .j-date { white-space: nowrap; color: var(--muted); font-variant-numeric: tabular-nums; }
.journal-table .j-who { white-space: nowrap; font-weight: 600; }
.journal-table .j-action { white-space: nowrap; }
.journal-table .j-detail { color: #444; }
.journal-table tr.journal-warn td { background: #fff7ed; }   /* échecs de connexion : ambre doux */
.journal-empty { color: var(--muted); font-size: 0.9rem; padding: 14px 4px; }

/* ── (v5.36) SURVEILLANCE HTTPS (vue admin) : panneau 3 niveaux (vert/orange/rouge). ── */
.https-panel { max-width: 620px; border: 1px solid var(--line); border-radius: var(--r-md, 12px); padding: 20px 24px; background: #fff; }
.https-panel.https-vert   { border-left: 5px solid #15803d; }
.https-panel.https-orange { border-left: 5px solid #d97706; background: #fffbf5; }
.https-panel.https-rouge  { border-left: 5px solid var(--err); background: #fef5f5; }
.https-headline { margin: 0 0 14px; font-size: 1rem; font-weight: 700; color: var(--brand-navy); }
.https-facts { margin: 0; display: flex; flex-direction: column; gap: 8px; }
.https-facts > div { display: flex; gap: 10px; font-size: 0.9rem; }
.https-facts dt { flex: 0 0 210px; color: var(--muted); font-weight: 600; }
.https-facts dd { margin: 0; color: var(--text); }
.https-note { margin: 16px 0 0; font-size: 0.84rem; color: var(--muted); line-height: 1.5; }
.https-loading { color: var(--muted); font-size: 0.9rem; }
