/*!
 * KD-STOCK v1.0 — Feuille de styles.
 * Copyright 2026 KIOMDA — Franck Sitbon. Tous droits reserves.
 * Developpement : fevrier 2026 a juin 2026.
 */
:root {
    --bg: #f4f6f8;
    --surface: #ffffff;
    --ink: #17202a;
    --muted: #687382;
    --line: #dce3ea;
    --primary: #FF6B26;
    --primary-dark: #B94412;
    --primary-soft: #FFF0E8;
    --danger: #b42318;
    --success: #197a50;
    --shadow: 0 12px 30px rgba(70, 42, 18, 0.08);
}

* { box-sizing: border-box; }
body {
    margin: 0;
    min-height: 100vh;
    /* Le FOND est choisi par l'utilisateur (users.bg_style -> <body data-bg="...">).
       Voir la section « Fonds de fenetre » plus bas. Le defaut est 'blueprint'. */
    background-color: var(--bg);
    background-attachment: fixed;
    color: var(--ink);
    font-family: Arial, Helvetica, sans-serif;
    font-size: 11px;
}

/* ===================== Fonds de fenetre (choix par utilisateur) =====================
   Chaque fond est declare DEUX fois : sur le <body> (le vrai fond) et sur
   .bg-swatch (la vignette du selecteur dans « Mon compte »), pour n'ecrire les
   degrades qu'une seule fois. Ajouter un fond = une entree dans window_backgrounds()
   (bootstrap) + une regle ici.
   'blueprint' est le fond HISTORIQUE : il reste le premier choix et le defaut.

   INTENSITE : toutes les opacites sont multipliees par --bg-i (1 = intensite
   d'origine). La variable est posee sur <body> depuis users.bg_intensity, et le
   curseur de « Mon compte » la modifie en direct (les vignettes en heritent). */
:root { --bg-i: 1; }

/* 1. Blueprint (actuel) — grille fine + lignes principales tous les 5 carreaux */
body[data-bg="blueprint"], .bg-swatch[data-bg="blueprint"] {
    background-color: #f4f6f8;
    background-image:
        linear-gradient(rgba(var(--bg-rgb, 45, 92, 138), calc(0.05 * var(--bg-i))) 1px, transparent 1px),
        linear-gradient(90deg, rgba(var(--bg-rgb, 45, 92, 138), calc(0.05 * var(--bg-i))) 1px, transparent 1px),
        linear-gradient(rgba(var(--bg-rgb, 45, 92, 138), calc(0.09 * var(--bg-i))) 1px, transparent 1px),
        linear-gradient(90deg, rgba(var(--bg-rgb, 45, 92, 138), calc(0.09 * var(--bg-i))) 1px, transparent 1px);
    background-size: 26px 26px, 26px 26px, 130px 130px, 130px 130px;
}
/* 2. Blueprint affinee */
body[data-bg="blueprint-soft"], .bg-swatch[data-bg="blueprint-soft"] {
    background-color: #f4f6f8;
    background-image:
        linear-gradient(rgba(var(--bg-rgb, 45, 92, 138), calc(0.035 * var(--bg-i))) 1px, transparent 1px),
        linear-gradient(90deg, rgba(var(--bg-rgb, 45, 92, 138), calc(0.035 * var(--bg-i))) 1px, transparent 1px),
        linear-gradient(rgba(var(--bg-rgb, 45, 92, 138), calc(0.06 * var(--bg-i))) 1px, transparent 1px),
        linear-gradient(90deg, rgba(var(--bg-rgb, 45, 92, 138), calc(0.06 * var(--bg-i))) 1px, transparent 1px);
    background-size: 28px 28px, 28px 28px, 140px 140px, 140px 140px;
}
/* 3. Grille de points */
body[data-bg="dots"], .bg-swatch[data-bg="dots"] {
    background-color: #f5f7f9;
    background-image: radial-gradient(rgba(var(--bg-rgb, 45, 92, 138), calc(0.18 * var(--bg-i))) 1px, transparent 1.4px);
    background-size: 22px 22px;
}
/* 4. Uni + halo chaud */
body[data-bg="halo"], .bg-swatch[data-bg="halo"] {
    background-color: #f4f6f8;
    background-image:
        radial-gradient(900px 420px at 12% -8%, rgba(var(--bg-rgb, 255, 107, 38), calc(0.10 * var(--bg-i))), transparent 60%),
        radial-gradient(760px 380px at 100% 108%, rgba(var(--bg-rgb, 45, 92, 138), calc(0.07 * var(--bg-i))), transparent 60%);
}
/* 5. Papier millimetre chaud */
body[data-bg="graph-warm"], .bg-swatch[data-bg="graph-warm"] {
    background-color: #f7f5f2;
    background-image:
        linear-gradient(rgba(var(--bg-rgb, 185, 68, 18), calc(0.045 * var(--bg-i))) 1px, transparent 1px),
        linear-gradient(90deg, rgba(var(--bg-rgb, 185, 68, 18), calc(0.045 * var(--bg-i))) 1px, transparent 1px),
        linear-gradient(rgba(var(--bg-rgb, 185, 68, 18), calc(0.08 * var(--bg-i))) 1px, transparent 1px),
        linear-gradient(90deg, rgba(var(--bg-rgb, 185, 68, 18), calc(0.08 * var(--bg-i))) 1px, transparent 1px);
    background-size: 24px 24px, 24px 24px, 120px 120px, 120px 120px;
}
/* 6. Hachures diagonales + halo */
body[data-bg="hatch"], .bg-swatch[data-bg="hatch"] {
    background-color: #f4f6f8;
    background-image:
        repeating-linear-gradient(45deg, rgba(var(--bg-rgb, 45, 92, 138), calc(0.035 * var(--bg-i))) 0 1px, transparent 1px 9px),
        radial-gradient(1000px 500px at 50% -10%, rgba(var(--bg-rgb, 255, 107, 38), calc(0.06 * var(--bg-i))), transparent 65%);
}
/* 7. Points + halo */
body[data-bg="dots-halo"], .bg-swatch[data-bg="dots-halo"] {
    background-color: #f5f7f9;
    background-image:
        radial-gradient(rgba(var(--bg-rgb, 45, 92, 138), calc(0.16 * var(--bg-i))) 1px, transparent 1.4px),
        radial-gradient(900px 430px at 10% -6%, rgba(var(--bg-rgb, 255, 107, 38), calc(0.09 * var(--bg-i))), transparent 60%);
    background-size: 22px 22px, 100% 100%;
}
/* 8. Trame isometrique (CAO) */
body[data-bg="iso"], .bg-swatch[data-bg="iso"] {
    background-color: #f4f6f8;
    background-image:
        repeating-linear-gradient(30deg, rgba(var(--bg-rgb, 45, 92, 138), calc(0.05 * var(--bg-i))) 0 1px, transparent 1px 30px),
        repeating-linear-gradient(-30deg, rgba(var(--bg-rgb, 45, 92, 138), calc(0.05 * var(--bg-i))) 0 1px, transparent 1px 30px),
        repeating-linear-gradient(90deg, rgba(var(--bg-rgb, 45, 92, 138), calc(0.035 * var(--bg-i))) 0 1px, transparent 1px 30px);
}
/* 9. Maille triangulaire (volumes) */
body[data-bg="triangles"], .bg-swatch[data-bg="triangles"] {
    background-color: #f5f6f8;
    background-image:
        repeating-linear-gradient(60deg, rgba(var(--bg-rgb, 45, 92, 138), calc(0.055 * var(--bg-i))) 0 1px, transparent 1px 44px),
        repeating-linear-gradient(-60deg, rgba(var(--bg-rgb, 45, 92, 138), calc(0.055 * var(--bg-i))) 0 1px, transparent 1px 44px),
        repeating-linear-gradient(0deg, rgba(var(--bg-rgb, 45, 92, 138), calc(0.045 * var(--bg-i))) 0 1px, transparent 1px 44px);
}
/* 10. Tole perforee (points en quinconce) */
body[data-bg="perf"], .bg-swatch[data-bg="perf"] {
    background-color: #f2f4f6;
    background-image:
        radial-gradient(rgba(var(--bg-rgb, 45, 92, 138), calc(0.13 * var(--bg-i))) 1.7px, transparent 2px),
        radial-gradient(rgba(var(--bg-rgb, 45, 92, 138), calc(0.13 * var(--bg-i))) 1.7px, transparent 2px);
    background-size: 26px 26px, 26px 26px;
    background-position: 0 0, 13px 13px;
}
/* 11. Grille + points d'intersection (bureau d'etudes) */
body[data-bg="cad"], .bg-swatch[data-bg="cad"] {
    background-color: #f4f6f8;
    background-image:
        radial-gradient(rgba(var(--bg-rgb, 45, 92, 138), calc(0.20 * var(--bg-i))) 1.2px, transparent 1.6px),
        linear-gradient(rgba(var(--bg-rgb, 45, 92, 138), calc(0.04 * var(--bg-i))) 1px, transparent 1px),
        linear-gradient(90deg, rgba(var(--bg-rgb, 45, 92, 138), calc(0.04 * var(--bg-i))) 1px, transparent 1px);
    background-size: 30px 30px, 30px 30px, 30px 30px;
}
/* 12. Rayonnage (etageres) */
body[data-bg="shelves"], .bg-swatch[data-bg="shelves"] {
    background-color: #f5f6f7;
    background-image:
        linear-gradient(rgba(var(--bg-rgb, 45, 92, 138), calc(0.075 * var(--bg-i))) 1px, transparent 1px),
        linear-gradient(90deg, rgba(var(--bg-rgb, 45, 92, 138), calc(0.035 * var(--bg-i))) 1px, transparent 1px);
    background-size: 100% 36px, 180px 100%;
}
/* 13. Grain papier + halo.
   Le bruit vient d'un SVG inline dont l'opacite est figee dans l'URL : elle ne peut
   pas suivre --bg-i. On le pose donc sur un calque ::before dont on fait varier
   l'opacite — c'est le seul fond a fonctionner ainsi. */
body[data-bg="grain"], .bg-swatch[data-bg="grain"] {
    position: relative;
    background-color: #f5f6f8;
    background-image:
        radial-gradient(900px 430px at 10% -6%, rgba(var(--bg-rgb, 255, 107, 38), calc(0.09 * var(--bg-i))), transparent 60%);
}
body[data-bg="grain"]::before, .bg-swatch[data-bg="grain"]::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: -1;
    opacity: calc(0.05 * var(--bg-i));
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2'/><feColorMatrix type='saturate' values='0'/></filter><rect width='140' height='140' filter='url(%23n)'/></svg>");
}
body[data-bg="grain"]::before { position: fixed; }

/* --- Selecteur de fond (page « Mon compte ») --- */
.bg-field { margin: 10px 0 4px; }
.bg-field-label { display: block; font-weight: 700; margin-bottom: 8px; }
.bg-picker { display: grid; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); gap: 10px; }
.bg-choice { cursor: pointer; display: block; }
.bg-choice input { position: absolute; opacity: 0; pointer-events: none; }
.bg-swatch {
    display: block;
    height: 66px;
    border: 2px solid var(--line);
    border-radius: 8px;
    /* La vignette ne doit PAS heriter du 'fixed' du body, sinon le motif ne se voit pas. */
    background-attachment: scroll !important;
    transition: border-color .15s, transform .1s;
}
.bg-choice:hover .bg-swatch { border-color: var(--primary); }
.bg-choice input:checked + .bg-swatch {
    border-color: var(--primary);
    box-shadow: 0 0 0 2px var(--primary-soft);
}
.bg-choice input:checked ~ .bg-name { color: var(--primary); font-weight: 800; }
.bg-name { display: block; margin-top: 5px; font-size: 10.5px; font-weight: 700; }
.bg-desc { display: block; font-size: 9.5px; color: var(--muted); line-height: 1.3; }

/* Curseur d'intensite du motif (previsualise en direct via --bg-i). */
.bg-intensity {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-top: 14px;
    padding: 10px 12px;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: #fff;
}
.bg-intensity label { font-weight: 700; white-space: nowrap; }
.bg-intensity input[type="range"] { flex: 1 1 auto; min-width: 140px; accent-color: var(--primary); }
.bg-intensity output {
    min-width: 46px;
    text-align: right;
    font-weight: 800;
    color: var(--primary);
    font-variant-numeric: tabular-nums;
}
.bg-intensity .bg-i-reset { flex: 0 0 auto; }

/* Couleur du motif : palette de pastilles (la 1re = couleur d'origine du fond). */
.bg-colors { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
.bg-colors-label { font-weight: 700; margin-right: 4px; }
.bg-color { cursor: pointer; display: inline-block; line-height: 0; }
.bg-color input { position: absolute; opacity: 0; pointer-events: none; }
.bg-color-dot {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border-radius: 999px;
    border: 2px solid #fff;
    box-shadow: 0 0 0 1px var(--line);
    font-size: 10px;
    font-weight: 800;
    color: #fff;
    transition: box-shadow .15s, transform .1s;
}
.bg-color:hover .bg-color-dot { transform: translateY(-1px); }
.bg-color input:checked + .bg-color-dot { box-shadow: 0 0 0 2px var(--primary); }
/* Pastille « couleur d'origine » : damier neutre. */
.bg-color-dot-auto {
    color: var(--muted);
    background:
        linear-gradient(45deg, #d9dee4 25%, transparent 25%),
        linear-gradient(-45deg, #d9dee4 25%, transparent 25%),
        linear-gradient(45deg, transparent 75%, #d9dee4 75%),
        linear-gradient(-45deg, transparent 75%, #d9dee4 75%);
    background-size: 8px 8px;
    background-position: 0 0, 0 4px, 4px -4px, -4px 0;
    background-color: #f2f4f6;
}
a { color: inherit; }
.sidebar {
    /* Doit tenir le libelle le plus long sans le couper (« Locations / Prets »,
       « Mouvements de stock »). La marge de .main suit la meme valeur. */
    width: 232px;
    /* Hauteur pilotee par inset (top:0/bottom:0) et non par 100vh : compatible avec
       le zoom global applique sur les grands ecrans (evite un vide en bas du menu). */
    padding: 8px 10px;
    background: #e5e5e5;
    color: #111111;
    display: flex;
    flex-direction: column;
    position: fixed;
    inset: 0 auto 0 0;
    overflow: hidden;
}
/* Petit bouton "livre" (manuel), tout en haut a gauche, au-dessus du logo. */
.manual-quick-btn {
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    margin: -4px 0 2px;
    border-radius: 5px;
    background: #ffffff;
    border: 1px solid #c9c9c9;
    box-shadow: 0 1px 2px rgba(0, 0, 0, .08);
    color: #b06a00;
    transition: background .15s, border-color .15s, transform .1s;
}
.manual-quick-btn svg { width: 11px; height: 11px; }
.manual-quick-btn:hover { background: #fff7ea; border-color: #b06a00; }
.manual-quick-btn:active { transform: translateY(1px); }
.manual-quick-btn.active { border-color: #b06a00; background: #fff2dd; }
.brand {
    position: relative;
    display: grid;
    justify-items: center;
    gap: 8px;
    padding: 0 6px 14px;
    border-bottom: 1px solid #c9c9c9;
    text-align: center;
}
/* Rangee du haut du menu : bouton Manuel (livre) + selecteur de taille (A / A)
   juste a sa droite. */
.sidebar-toprow {
    align-self: flex-start;
    display: flex;
    align-items: center;
    gap: 6px;
    margin: -4px 0 2px;
}
.sidebar-toprow .manual-quick-btn { margin: 0; }
/* Selecteur de taille d'interface (petit A / gros A). */
.ui-size-switch {
    display: inline-flex;
    align-items: center;
    gap: 3px;
}
.ui-size-btn {
    /* Meme dimension que le bouton Manuel (20x20) ; seule la lettre A change de
       taille selon la taille d'interface qu'elle represente. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    width: 20px;
    height: 20px;
    border: 1px solid #c9c9c9;
    border-radius: 5px;
    background: #ffffff;
    color: #555;
    font-weight: 800;
    line-height: 1;
    cursor: pointer;
    transition: background .15s, border-color .15s, color .15s;
}
.ui-size-btn[data-ui-size="1"] { font-size: 7px; }
.ui-size-btn[data-ui-size="2"] { font-size: 10px; }
.ui-size-btn[data-ui-size="3"] { font-size: 13px; }
.ui-size-btn:hover { border-color: var(--primary); color: var(--primary); }
.ui-size-btn.is-active {
    background: var(--primary);
    border-color: var(--primary);
    color: #fff;
}
.brand img {
    display: block;
    width: 100%;
    max-height: 92px;
    object-fit: contain;
}
.brand span, .eyebrow, .sku {
    display: block;
    color: var(--muted);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0;
    text-transform: uppercase;
}
.brand-logo {
    display: block;
    width: 100%;
    max-width: 150px;
    height: auto;
    margin: 0 auto;
}
.brand-user {
    display: block;
    margin-top: 6px;
    font-size: 11.5px;
    font-weight: 800;
    color: var(--primary-dark);
    text-transform: none;
    letter-spacing: 0;
}
.brand-logo-row { display: flex; align-items: flex-start; justify-content: center; gap: 6px; }
/* Selecteur renforce : .brand span imposerait sinon une couleur grise. */
.brand .brand-version {
    flex: 0 0 auto; align-self: center;
    background: var(--primary); color: #fff;
    font-size: 10px; font-weight: 800; line-height: 1;
    padding: 3px 7px; border-radius: 999px; letter-spacing: 0; text-transform: none;
}
nav { display: grid; gap: 4px; margin-top: 14px; flex: 1 1 auto; min-height: 0; overflow-y: auto; align-content: start; }

.nav-item {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 5px 8px;
    font-size: 11px;
    font-family: inherit;
    border: none;
    background: none;
    border-radius: 8px;
    color: #111111;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
}
.nav-item:hover { background: #d6d6d6; }
.nav-item.active { background: var(--primary); color: #fff; }
.nav-logout-form { margin: 0; }
.nav-logout { color: #b23b3b; }
.nav-logout:hover { background: #f6dada; color: #8f2a2a; }

/* Acces en LECTURE SEULE (utilisateur normal).
   C'est de l'ERGONOMIE, pas de la securite : la protection reelle est cote serveur
   (handle_post() exige l'admin, require_editor() refuse les ecrans de modification).
   Ici on evite seulement de montrer des commandes qui seraient refusees.

   La regle REPREND CELLE DU SERVEUR : on interdit TOUT formulaire d'ecriture
   (method=post), puis on autorise nommement les exceptions. Enumerer les verbes
   a interdire (save_, delete_, ...) etait voue a l'echec : « duplicate_ » y avait
   deja echappe. Les formulaires de RECHERCHE sont en method=get : ils ne sont pas
   concernes. */
/* NOTE : le masquage des formulaires POST, des boutons [formaction] et des liens
   vers un ecran de modification n'est PLUS ici. Il est genere pour l'utilisateur
   courant par readonly_style_css() (app/bootstrap.php), qui en exclut ce qu'il a
   le droit de faire. Une regle figee ici masquerait aussi les actions autorisees.
   Ce qui suit ne concerne que des blocs entiers, sans droit associe. */
/* Choix de boitiers a louer : sans le bouton, les cases n'ont plus d'objet. */
.role-readonly .rental-select-bar,
.role-readonly [data-select-box],
.role-readonly .rental-picker { display: none !important; }

/* --- Les EXCEPTIONS : ce qui ne touche que l'utilisateur lui-meme. ---
   Elles doivent etre AU MOINS aussi specifiques que la regle d'interdiction
   ci-dessus (« .role-readonly form[method=post] », soit 0-2-1) : entre deux
   declarations !important, c'est la specificite qui tranche, pas l'ordre.
   « .role-readonly .nav-logout-form » (0-2-0) perdait — et enfermait
   l'utilisateur dehors de sa propre deconnexion. */
/* On cible l'ACTION et non la classe : une classe se renomme (nav-logout-form ->
   topbar-logout-form) sans que la regle suive, et l'utilisateur se retrouve sans
   bouton de deconnexion. L'action, elle, est le contrat. */
.role-readonly form[action*="backoffice_logout"],
.role-readonly form[action*="save_my_account"] { display: block !important; }
.role-readonly form[action*="backoffice_logout"] { display: flex !important; }
.role-readonly .account-form .button.primary { display: inline-flex !important; }
/* La colonne d'action ne doit pas laisser un vide : « Voir » y reste. */
.role-readonly a[href*="_view"] { display: inline-flex; }
/* FICHE CLIENT consultable par tous. Sans ces deux exceptions elle serait invisible
   aux non-admins : la fiche EST un <form method="post"> (masque par la regle
   ci-dessus), et la liste y renvoie par des liens « ..._form ». Les champs sont
   neutralises cote serveur (<fieldset disabled>), les boutons d'ecriture ne sont pas
   rendus, et save/delete/merge_client restent refuses par le garde POST.
   Meme specificite que l'interdiction (0-2-1 / 0-2-0), et posees APRES elle. */
.role-readonly form[action*="save_client"] { display: grid !important; }
.role-readonly a[href*="client_form"] { display: inline !important; }
/* Marquer une activite lue/non lue n'est pas une modification de donnees metier :
   chacun gere son propre fil. */
.role-readonly .activity-mark,
.role-readonly .activity-unmark,
.role-readonly [data-activity-all],
.role-readonly [data-activity-toggle-read] { display: inline-flex !important; }
.role-readonly .activity-mark[hidden],
.role-readonly .activity-unmark[hidden],
.role-readonly [data-activity-all][hidden],
.role-readonly [data-activity-toggle-read][hidden] { display: none !important; }
.field-locked { background: #f1f3f5 !important; color: #6b7280 !important; cursor: not-allowed !important; }
.nav-label { font-weight: 700; }

/* Icone : trait noir sur petit fond gris (independant de l'etat actif). */
.nav-icon {
    flex: none;
    width: 22px;
    height: 22px;
    display: grid;
    place-items: center;
    border-radius: 6px;
    background: #c9c9c9;
    color: #111111;
}
.nav-icon svg { width: 14px; height: 14px; display: block; }

/* ACCORDEON : une seule rubrique ouverte a la fois. On clique le titre, sa portion
   se deploie et la precedente se referme (le choix « un seul ouvert » est tenu par
   setupNavAccordion() dans app.js ; ici on ne fait que montrer/cacher).
   La rubrique de la page courante recoit .open cote serveur : au chargement, le
   menu s'ouvre deja au bon endroit. */
/* La fleche est ecrite AVANT le badge d'acces dans le HTML ; « order » la renvoie
   a l'extreme droite, ou on l'attend sur un accordeon. Le badge, lui, garde son
   margin-left:auto et pousse les deux vers la droite. */
.nav-arrow {
    order: 10;
    flex: none;
    display: inline-flex;
    transition: transform .15s ease;
    color: var(--primary-dark);
}
/* Sans badge, c'est la fleche qui doit pousser a droite. */
.nav-parent .nav-arrow:last-child { margin-left: auto; }
.nav-arrow svg { width: 11px; height: 11px; display: block; }
.nav-group.open > .nav-parent .nav-arrow { transform: rotate(90deg); }
/* Le sous-menu n'existe visuellement que si la rubrique est ouverte. */
.nav-group > .nav-sub { display: none; }
.nav-group.open > .nav-sub { display: grid; }

.nav-group { display: grid; gap: 2px; border: 1px solid #cfcfcf; border-radius: 10px; padding: 5px; background: #ededed; }
.nav-group .nav-parent {
    cursor: pointer;
    width: 100%;
    text-align: left;
    background: #FFF1E8;          /* orange tres pale */
    border-bottom: 2px solid var(--primary);
    border-radius: 6px;
    padding: 4px 7px;
    margin-bottom: 3px;
}
/* Une rubrique refermee n'a pas de sous-menu dessous : la marge ferait un blanc. */
.nav-group:not(.open) .nav-parent { margin-bottom: 0; }
.nav-group .nav-parent:hover { background: #FFE3D2; }
.nav-group .nav-parent:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
.nav-group .nav-parent .nav-label { font-size: 9px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--primary-dark); font-weight: 800; }
.nav-group .nav-parent .nav-icon { width: 19px; height: 19px; }
.nav-group .nav-parent .nav-icon svg { width: 13px; height: 13px; }
.nav-sub { display: grid; gap: 3px; }
/* Sous-rubriques Entrees / Sorties dans Mouvements de stock */
.nav-subgroup { display: grid; gap: 3px; padding: 4px 4px 5px; border-radius: 8px; background: #e3e3e3; }
.nav-subgroup-in { background: #e6f6ea; }   /* vert tres pale (entrees) */
.nav-subgroup-out { background: #fdeaea; }  /* rouge tres pale (sorties) */
.nav-subhead { display: flex; align-items: center; font-size: 8px; letter-spacing: 0.05em; text-transform: uppercase; font-weight: 800; color: var(--primary-dark); padding: 1px 6px 1px; }
.nav-subhead-arrow { margin-left: auto; display: inline-block; font-size: 28px; line-height: 1; font-weight: 900; -webkit-text-stroke: 2px currentColor; text-stroke: 2px currentColor; }
/* Meme glyphe (->) pour les deux ; on pivote celui des entrees -> tailles identiques. */
.nav-subgroup-in .nav-subhead-arrow { color: #1c7a3f; transform: rotate(180deg); }
.nav-subgroup-out .nav-subhead-arrow { color: #b42318; }
.nav-subitem { padding: 4px 8px; font-size: 10.5px; }
.nav-subitem .nav-icon { width: 19px; height: 19px; }
.nav-subitem .nav-icon svg { width: 13px; height: 13px; }

.nav-badge {
    margin-left: auto;
    min-width: 18px;
    height: 18px;
    display: inline-grid;
    place-items: center;
    padding: 0 5px;
    border-radius: 999px;
    background: var(--danger);
    color: #fff;
    font-size: 10px;
    line-height: 1;
}


/* Doit rester egal a la largeur de .sidebar (fixe) : sinon le contenu passe
   dessous ou laisse un vide. */
.main { margin-left: 232px; padding: 18px; min-width: 0; }
/* Interface compacte sur grand ecran : un zoom global reduit d'un coup TOUT
   (textes, boutons, cartes, graphiques, images, espacements) pour tenir
   confortablement sur un ecran 1920x1080. Le mobile (< 861px, menu tiroir) n'est
   pas concerne.
   Trois tailles au choix (boutons A / A / A en haut du menu), memorisees PAR
   UTILISATEUR en base :
   - Taille 1 (petit A)   = 110% de la base 0.82   = 0.902   (defaut)
   - Taille 2 (moyen A)   = 125% de la base 0.82   = 1.025
   - Taille 3 (gros A)    = 110% de la taille 2    = 1.1275
   L'attribut data-ui-size est rendu cote serveur sur <html> (valeur en base). */
@media (min-width: 861px) {
    body { zoom: 0.902; }
    html[data-ui-size="2"] body { zoom: 1.025; }
    html[data-ui-size="3"] body { zoom: 1.1275; }
}
.page-header {
    display: flex;
    /* Les boutons d'action ne flottent plus a droite : ils se placent SOUS le fil
       d'Ariane et ALIGNES A GAUCHE, au meme endroit sur toutes les pages. */
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;
    min-height: 30px;      /* fil d'Ariane toujours a la meme hauteur */
    /* Encadre au filet orange, comme les panneaux : memes valeurs que la regle
       « .stats article, .stat-card, .panel, .item-card » plus haut. */
    padding: 10px 13px;
    border: 1px solid var(--primary);
    border-radius: 8px;
    background: var(--surface);
    box-shadow: var(--shadow);
    /* Respiration en haut de page : le fil d'Ariane etait colle au bord. Comme
       .page-header est le premier element du contenu, ce decalage descend AUSSI
       les boutons d'action et tout ce qui suit — sur toutes les pages. */
    margin-top: 6px;
    /* Une ligne de texte d'air sous la barre d'action, pour qu'elle ne colle pas
       au premier panneau (18px + ~14px = une hauteur de ligne). */
    margin-bottom: 32px;
}
/* Le groupe de boutons garde sa mise en ligne, sans s'etirer. */
/* La barre d'action tient sur UNE SEULE ligne sur toutes les pages (au plus 5
   boutons, fiche boitier comprise). Si la largeur ne suffit vraiment pas, elle
   defile horizontalement plutot que de passer a la ligne et de decaler la page. */
.page-header > .form-actions {
    margin: 0;
    flex-wrap: nowrap;
    justify-content: flex-start;
    gap: 10px;
    max-width: 100%;
    overflow-x: auto;
    /* Sans ceci, la barre coupe l'ombre portee des boutons en haut et en bas. */
    padding: 2px 0;
}
/* Un libelle long ne doit pas se casser en deux lignes dans son bouton. */
.page-header .button { white-space: nowrap; }
.page-header .form-actions > form { flex: 0 0 auto; }

/* Boutons d'action de l'en-tete : un peu plus marques que les boutons ordinaires
   (ils portent l'action principale de la page) mais SANS surdimensionner la barre.
   On ne touche ici qu'a la taille et au relief : la couleur reste celle de la
   variante (.primary, .danger...), sinon on ecraserait le bouton principal. */
.page-header .button {
    min-height: 29px;
    padding: 5px 11px;
    font-size: 12px;
    font-weight: 700;
    border-radius: 7px;
    box-shadow: 0 1px 2px rgba(20, 30, 45, .10);
}
.page-header .button:hover { box-shadow: 0 2px 7px rgba(20, 30, 45, .18); }
/* Les boutons secondaires (sans variante de couleur) etaient trop discrets :
   fond franc et bordure plus marquee pour qu'ils se lisent comme des boutons. */
.page-header .button:not(.primary):not(.danger):not(.soft-green):not(.success):not(.draft) {
    background: var(--surface, #fff);
    border-color: #c3ccd8;
}
.page-header .button:not(.primary):not(.danger):not(.soft-green):not(.success):not(.draft):hover { border-color: #94a3b5; }
/* Le bouton principal ressort par une ombre coloree. */
.page-header .button.primary { box-shadow: 0 2px 7px rgba(29, 111, 208, .32); }
.page-header .button.primary:hover { box-shadow: 0 3px 11px rgba(29, 111, 208, .42); }
/* En-tetes de page en FIL D'ARIANE : « Accueil › Section › Titre », avec une SEULE
   taille de police pour les titres (l'ancien gros h1 devient un segment du fil). */
.page-header > div { display: flex; flex-wrap: wrap; align-items: baseline; gap: 3px 8px; min-width: 0; }
.page-header .eyebrow, .page-header h1 { font-size: 14px !important; margin: 0; line-height: 1.3; letter-spacing: 0; text-transform: none; }
.page-header .eyebrow { color: var(--muted); font-weight: 600; }
.page-header h1 { color: var(--ink); font-weight: 800; }
.page-header .eyebrow::after { content: "\203A"; margin-left: 8px; color: var(--muted); font-weight: 400; }
.breadcrumb-home { color: var(--muted); font-weight: 600; font-size: 14px; text-decoration: none; }
.breadcrumb-home::after { content: "\203A"; margin-left: 8px; color: var(--muted); font-weight: 400; }
.breadcrumb-home:hover { color: var(--primary-dark); }
h1, h2, p { margin-top: 0; }
h1 { margin-bottom: 0; font-size: 17px; }
h2 { font-size: 13px; }
.button {
    display: inline-flex;
    min-height: 27px;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 5px 9px;
    border: 1px solid var(--line);
    border-radius: 7px;
    background: #fff;
    color: var(--ink);
    font-weight: 700;
    font-size: 10.5px;
    text-decoration: none;
    cursor: pointer;
}
/* Icone associee automatiquement a chaque bouton (voir setupButtonIcons). */
.btn-ico { width: 13px; height: 13px; fill: currentColor; flex: none; }
.button.small .btn-ico { width: 11px; height: 11px; }
.page-header .button .btn-ico { width: 13px; height: 13px; }

.button.primary { border-color: var(--primary); background: var(--primary); color: #fff; }
.button.primary:hover { border-color: var(--primary-dark); background: var(--primary-dark); }
.button.danger, .danger { color: var(--danger); }
/* L'attribut [hidden] doit l'emporter sur display:inline-flex ci-dessus (sinon les
   boutons "hidden" — ex. Marquer lu / non lu — resteraient visibles tous les deux). */
.button[hidden] { display: none !important; }
.stats {
    display: grid;
    /* auto-fit : les colonnes se replient au lieu de deborder a droite (sous le
       menu fixe) sur les ecrans/orientations etroits. */
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 12px;
    margin-bottom: 16px;
}
/* TOUS les blocs de l'application (panneaux, cartes, compteurs) portent le meme
   fin lisere orange. Regle unique et centrale : c'est ici qu'on change la teinte
   des blocs, pas au cas par cas. Les etats particuliers (stock bas, retard,
   piece manquante) reprennent la main plus bas avec leur propre couleur. */
.stats article, .stat-card, .panel, .item-card {
    border: 1px solid var(--primary);
    border-radius: 8px;
    background: var(--surface);
    box-shadow: var(--shadow);
}
.stats article { padding: 12px; }

.stat-card {
    display: block;
    padding: 12px;
    color: inherit;
    text-decoration: none;
}
.stat-card.has-alerts,
.alert-panel {
    background: #fff1f1;
    border-color: #f3b6b6;
}
.stat-card.has-alerts:hover {
    background: #ffe7e7;
}

.stats span { color: var(--muted); font-weight: 700; }
.stats strong { display: block; margin-top: 6px; font-size: 16px; }
/* Les blocs de premier niveau d'une page ne doivent pas se toucher : sans cet
   ecart, deux panneaux successifs se lisent comme un seul bloc a deux liserés. */
.panel { padding: 13px; }
.main > .panel, .main > .stats { margin-bottom: 5px; }
.table-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 5px 7px; border-bottom: 1px solid var(--line); text-align: left; vertical-align: top; }
th { color: var(--muted); font-size: 9.5px; }
.search { display: flex; gap: 10px; margin-bottom: 20px; }
.search-clear { flex: 0 0 auto; min-width: 40px; padding-left: 10px; padding-right: 10px; font-size: 15px; font-weight: 800; line-height: 1; }
/* Filtres / tri (liste pieces et assemblages). */
.search.filters { flex-wrap: wrap; align-items: flex-end; }
.filter-field { display: flex; flex-direction: column; gap: 3px; font-size: 10px; font-weight: 700; color: var(--muted); }
.filter-field select { min-height: 38px; }

/* Petit bouton « graphique » en haut a gauche de l'image (ouvre le pop-up). */
.card-graph-btn { position: absolute; top: 5px; left: 5px; z-index: 4; width: 21px; height: 21px; padding: 0;
    display: grid; place-items: center; border: 1px solid rgba(0, 0, 0, 0.08); border-radius: 6px;
    background: rgba(233, 235, 238, 0.92); color: var(--primary); cursor: pointer;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12); }
.card-graph-btn svg { width: 13px; height: 13px; }
.card-graph-btn:hover { background: #fff; border-color: var(--primary); color: var(--primary-dark); }
/* Mini-graphique de tendance : retire des cartes, accessible seulement via le bouton
   « graphique ». On conserve l'element dans le DOM (donnees + SVG clones par le pop-up)
   mais on ne l'affiche plus sur la carte. */
.card-spark { display: none; }
.spark-modal { position: fixed; inset: 0; z-index: 1100; background: rgba(20, 23, 28, 0.55); display: grid; place-items: center; padding: 18px; }
.spark-modal[hidden] { display: none; }
.spark-modal-inner { background: #fff; border-radius: 14px; max-width: 560px; width: 100%; max-height: 90vh; overflow: auto; padding: 18px 20px; position: relative; box-shadow: var(--shadow); }
.spark-modal-close { position: absolute; top: 12px; right: 12px; }
.spark-modal-inner h2 { margin: 0 70px 12px 0; font-size: 16px; }
.spark-modal-chart { background: #f6f8fa; border: 1px solid var(--line); border-radius: 10px; padding: 8px; }
/* Meme forme (etiree) que le mini-graphe des cartes, lignes un peu plus grasses. */
.spark-modal-chart svg { width: 100%; height: 110px; display: block; }
.spark-modal-chart .sc-line { fill: none; stroke: #2f6bd8; stroke-width: 2.6; vector-effect: non-scaling-stroke; }
.spark-modal-chart .sc-trend { fill: none; stroke: #FF6B26; stroke-width: 2; stroke-dasharray: 6 4; vector-effect: non-scaling-stroke; }
.spark-modal-chart .sc-threshold { stroke: #b42318; stroke-width: 1.8; stroke-dasharray: 4 3; vector-effect: non-scaling-stroke; }
.spark-modal-chart .sc-dot { fill: #2f6bd8; r: 3; }
.spark-modal-figures { list-style: none; margin: 14px 0; padding: 0; display: grid; gap: 4px; }
.spark-modal-figures li { display: flex; justify-content: space-between; gap: 12px; padding: 6px 9px; border-radius: 7px; background: #f6f8fa; font-size: 12px; }
.spark-fig-label { color: var(--muted); }
.spark-fig-val { font-weight: 800; }
.spark-modal-help h3 { font-size: 13px; margin: 12px 0 6px; }
.spark-modal-help p { font-size: 11.5px; line-height: 1.45; margin: 6px 0; }
.spark-modal-help ul { margin: 6px 0; padding-left: 0; list-style: none; display: grid; gap: 5px; }
.spark-modal-help li { font-size: 11.5px; line-height: 1.4; }
.lg { display: inline-block; width: 22px; height: 9px; vertical-align: middle; margin-right: 4px; }
.lg-line { border-top: 3px solid var(--primary); height: 0; }
.lg-trend { border-top: 2px dashed #8a6d00; height: 0; }
.lg-threshold { border-top: 2px dotted var(--danger); height: 0; }
.lg-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--primary); }
input, select, textarea {
    width: 100%;
    min-height: 31px;
    padding: 6px 9px;
    border: 1px solid var(--line);
    border-radius: 7px;
    background: #fff;
    color: var(--ink);
    font: inherit;
    font-size: 11px;
}
/* Un champ date n'a que « jj/mm/aaaa » + son icone a montrer : le laisser occuper
   toute sa colonne le rend disproportionne face aux champs texte, et sa largeur
   intrinseque incompressible le faisait deborder sur le champ voisin. Plafond pose
   ICI (avant .date-wrap plus bas, qui le neutralise pour les champs a croix). */
.form input[type="date"] { max-width: 175px; }
label { display: grid; gap: 5px; color: var(--muted); font-size: 10px; font-weight: 700; }
/* Liste deroulante avec filtre integre (combobox) */
.combo { position: relative; }
.combo-native { position: absolute; width: 1px; height: 1px; min-height: 0; padding: 0; margin: -1px; opacity: 0; pointer-events: none; }
.combo-input { width: 100%; }
.combo-list { position: absolute; left: 0; right: 0; top: 100%; z-index: 60; margin: 3px 0 0; padding: 4px; list-style: none; max-height: 240px; overflow-y: auto; background: #fff; border: 1px solid var(--line); border-radius: 8px; box-shadow: var(--shadow); }
.combo-item { padding: 6px 9px; border-radius: 6px; font-size: 12px; line-height: 1.3; cursor: pointer; }
.combo-item.active, .combo-item:hover { background: var(--primary-soft); }
.combo-item.selected { font-weight: 700; }
.combo-empty { padding: 6px 9px; color: var(--muted); font-size: 12px; }
.grid-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(168px, 1fr)); gap: 9px; }
/* Cartes pieces / assemblages plus petites (vue cartes uniquement). */
#parts-grid:not(.list-view), #assemblies-grid:not(.list-view) { grid-template-columns: repeat(auto-fill, minmax(128px, 1fr)); gap: 8px; }
#parts-grid:not(.list-view) .item-body, #assemblies-grid:not(.list-view) .item-body { padding: 6px 7px; font-size: 9.5px; }
#parts-grid:not(.list-view) .item-body h2, #assemblies-grid:not(.list-view) .item-body h2 { font-size: 10px; margin: 2px 0 3px; }
#parts-grid:not(.list-view) .item-body .sku, #assemblies-grid:not(.list-view) .item-body .sku { font-size: 8.5px; }
#parts-grid:not(.list-view) .item-body > p, #assemblies-grid:not(.list-view) .item-body > p { font-size: 9.5px; margin: 1px 0; }
#parts-grid:not(.list-view) .item-card .actions .button, #assemblies-grid:not(.list-view) .item-card .actions .button { font-size: 9.5px; padding-top: 4px; padding-bottom: 4px; }
.item-card { overflow: hidden; }
/* (Le lisere orange des cartes est desormais porte par la regle commune des blocs,
   plus haut : .stats article, .stat-card, .panel, .item-card.) */
.item-card.low-stock-card {
    background: #fff1f1;
    border-color: #f3b6b6;
}
.item-card.low-stock-card .item-body {
    background: rgba(255, 241, 241, 0.82);
}
/* Cartes pieces/assemblages : colonne -> boutons d'action alignes EN BAS, et plus fins.
   IMPORTANT : :not([hidden]) pour ne pas reafficher les cartes masquees par le filtre. */
/* Toutes les cartes de contenu (pieces, assemblages, vues eclatees, documents) :
   le corps prend la hauteur disponible et la barre de boutons est epinglee en bas,
   pour que les boutons soient toujours alignes au meme niveau d'une carte a l'autre. */
.grid-list:not(.list-view):not(.bo-grid) .item-card:not([hidden]) { display: flex; flex-direction: column; }
.grid-list:not(.list-view):not(.bo-grid) .item-card > .item-body { flex: 1 1 auto; display: flex; flex-direction: column; }
.grid-list:not(.list-view):not(.bo-grid) .item-body .actions { margin-top: auto; }
.grid-list:not(.list-view):not(.bo-grid) .actions .button { padding-top: 3px; padding-bottom: 3px; min-height: 0; }

.item-body { padding: 8px; font-size: 10px; }
.item-body h2 { margin: 3px 0 4px; font-size: 11px; }
.media-stack { display: grid; gap: 6px; }
/* Zone image toujours carree (1:1) */
/* Pas de cadre autour de la photo : juste une ligne de separation en bas, qui
   prolonge le lisere de la carte et detache proprement l'image du texte. */
.media-photo { position: relative; aspect-ratio: 1 / 1; border-bottom: 1px solid var(--primary); }
.part-photo, .empty-media { width: 100%; height: 100%; background: #e8eef2; border: 0; }
.part-photo { display: block; object-fit: cover; }
.empty-media { display: grid; place-items: center; color: var(--muted); font-weight: 700; }
/* Boutons de visualisation poses sur la photo : STL a gauche, 3D a droite. */
.media-view-buttons { position: absolute; left: 0; right: 0; bottom: 0; display: flex; align-items: center; gap: 6px; padding: 5px; pointer-events: none; }
.media-view-btn {
    pointer-events: auto;
    min-width: 34px;
    padding: 3px 9px;
    border: none;
    border-radius: 999px;
    background: rgba(20, 23, 28, 0.72);
    color: #fff;
    font-weight: 800;
    font-size: 9px;
    letter-spacing: 0.02em;
    cursor: pointer;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
    transition: background 0.15s ease;
}
.media-view-btn:hover { background: var(--primary); }
/* Bouton/etiquette STL en BLEU pour le distinguer du 3D. */
.media-view-stl { background: #1763c2; }
.media-view-stl:hover { background: #0f4d9c; }
.file-kind.stl { color: #1763c2; }
.media-view-3d { margin-left: auto; } /* pousse le bouton 3D vers la droite */
.model-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 0 12px 9px; }
.file-link { color: var(--primary-dark); font-weight: 700; font-size: 11px; }

/* Visualiseur 3D en modale (three.js) */
.model-modal {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background: rgba(53, 55, 50, 0.72);
}
.model-modal[hidden] { display: none; }
.model-modal-inner {
    position: relative;
    /* Zone carree : cote = la plus petite dimension disponible de l'ecran */
    width: min(82vh, 100%);
    aspect-ratio: 1 / 1;
    height: auto;
    max-height: 100%;
    background: #f5f6f8;
    border-radius: 14px;
    overflow: hidden;
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.45);
}
.model-modal-viewer { position: relative; width: 100%; height: 100%; }
.model-modal-close { position: absolute; top: 12px; right: 12px; z-index: 2; }
.model-modal-bambu { position: absolute; top: 12px; left: 12px; z-index: 2; display: inline-flex; align-items: center; gap: 7px; text-decoration: none; }
.model-modal-bambu[hidden] { display: none; }
.model-modal-bambu .bambu-logo { flex: 0 0 auto; border-radius: 5px; }

/* Canvas du rendu 3D (three.js) */
.model-inline, .model-modal-viewer { position: relative; }
.model-inline canvas, .model-modal-viewer canvas { display: block; width: 100%; height: 100%; outline: none; }
.model-status {
    position: absolute;
    inset: 0;
    margin: 0;
    display: grid;
    place-items: center;
    padding: 12px;
    text-align: center;
    color: var(--muted);
    font-size: 12px;
    font-weight: 700;
    background: #f5f6f8;
    pointer-events: none;
}
/* `display: grid` ci-dessus ecrase le display:none de l'attribut [hidden]
   (regle navigateur, faible priorite) : sans ceci, l'overlay "Chargement"
   reste affiche et masque le modele rendu derriere. */
.model-status[hidden] { display: none; }
/* Bouton "retablir la vue initiale", en bas a droite de chaque rendu 3D. */
.model-reset-btn {
    position: absolute;
    right: 8px;
    bottom: 8px;
    z-index: 3;
    width: 30px;
    height: 30px;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    border-radius: 8px;
    background: rgba(20, 23, 28, 0.62);
    color: #fff;
    font-size: 15px;
    line-height: 1;
    cursor: pointer;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
    transition: background 0.15s ease;
}
.model-reset-btn:hover { background: var(--primary); }
.model-reset-btn[hidden] { display: none; }
.pdf-media { color: var(--primary-dark); letter-spacing: 1px; }
.stock-line, .actions, .form-actions, .assembly-row {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}
.stock-line { justify-content: space-between; padding: 7px 0 10px; color: var(--muted); font-size: 11px; }
/* Boutons du bas des cartes (pieces, assemblages, vues eclatees, documents) :
   meme largeur, repartis sur toute la largeur de la carte. */
.item-card { position: relative; }
.item-card .actions { display: flex; flex-wrap: nowrap; gap: 8px; justify-content: center; align-items: stretch; }
.item-card .actions > * { flex: 1 1 0; min-width: 0; }
.item-card .actions form { display: flex; margin: 0; }
.item-card .actions .button { width: 100%; padding-left: 6px; padding-right: 6px; }
/* Le bouton de suppression (croix ou "Supprimer") a la MEME taille que les autres. */
.item-card .actions .icon-del { flex: 1 1 0 !important; width: 100% !important; min-width: 0 !important; }
/* Pastille stock en haut a droite de la photo (vert/rouge sur fond pale). */
.stock-pill { position: absolute; top: 6px; right: 6px; z-index: 3; padding: 1px 8px; border-radius: 999px; font-weight: 800; font-size: 10px; background: #fff; border: 1px solid var(--line); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15); }
.stock-pill-pos { color: var(--success); }
.stock-pill-neg { color: var(--danger); }
.stock-pill-zero { color: var(--muted); }
/* Connexion : case "se souvenir", lien mot de passe oublie. */
.login-remember { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--muted); }
.login-forgot { text-align: center; margin: 6px 0 0; }
.ex3d-tools { flex-wrap: wrap; gap: 10px; }
/* Valeur de stock : vert gras si positive, rouge gras si negative. */
.stock-pos { color: var(--success); font-weight: 800; }
.stock-neg { color: var(--danger); font-weight: 800; }
.stock-zero { font-weight: 800; }
/* Ligne 'Stock actuel' (formulaire assemblage) : valeur agrandie. */
.stock-actuel-line .stock-pos, .stock-actuel-line .stock-neg, .stock-actuel-line .stock-zero { font-size: 14px; }
/* Date fabricable d'un assemblage (commandes en attente). */
.buildable-tag { display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: 11px; font-weight: 800; }
.buildable-now { background: #e4f6ea; color: #1c7a3f; }
.buildable-soon { background: #fff1e8; color: var(--primary-dark); }
.buildable-blocked { background: #fde7e3; color: #b42318; }
.order-received { opacity: 0.55; }
/* Cartes : commande a venir (pieces) + date fabricable (assemblages). */
.card-incoming { margin: 4px 0 2px; font-size: 11px; font-weight: 700; color: #2f6bd8; }
.card-buildable { margin: 4px 0 2px; }
/* Sur les cartes en grille (compactes), les indications de fabricabilite sont
   reduites et bornees a 2 lignes maximum pour rester lisibles. */
#parts-grid:not(.list-view) .card-incoming,
#assemblies-grid:not(.list-view) .buildable-badge,
#assemblies-grid:not(.list-view) .shortage-badge,
#assemblies-grid:not(.list-view) .card-buildable .buildable-tag {
    font-size: 9px;
    line-height: 1.2;
    white-space: normal;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
/* Cellule de prevision de fabrication : pastilles qui se replient. */
.forecast-cell { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
/* Alerte 'a commander' sur le tableau de bord. */
.reorder-panel { border-left: 4px solid var(--primary); }
.reorder-count { display: inline-grid; place-items: center; min-width: 22px; height: 22px; padding: 0 6px; border-radius: 999px; background: var(--primary-soft); color: var(--primary-dark); font-size: 11px; font-weight: 800; }
.reorder-count.is-late { background: #fde7e3; color: #b42318; }
.reorder-row-late td { background: #fff6f4; }
/* Jalon "fabricable" sur le graphique d'evolution du stock. */
.stock-chart .sc-milestone { stroke: #197a50; stroke-width: 2; stroke-dasharray: 4 3; }
.stock-chart .sc-milestone-dot { fill: #197a50; }
.stock-chart .sc-milestone-label { fill: #197a50; font-size: 10px; font-weight: 700; }

/* Fiche fournisseur : pleine largeur de la page. */
.supplier-form { max-width: none; }

/* Vignette PDF (1re page) cliquable -> ouvre le lecteur en page. */
.pdf-thumb-btn { display: block; width: 100%; padding: 0; border: 0; background: none; cursor: pointer; }
.pdf-thumb-btn .part-photo { display: block; width: 100%; height: auto; max-height: 240px; object-fit: contain; background: #fff; }

/* Documents techniques : titre de categorie en pleine largeur de la grille. */
#schematics-grid .schematic-cat-title { grid-column: 1 / -1; margin: 10px 0 2px; }
/* Vue liste : vignette a gauche, a la hauteur (reduite) de la ligne. */
.grid-list.list-view .pdf-thumb-btn { height: 100%; }
.grid-list.list-view .pdf-thumb-btn .part-photo { height: 66px; min-height: 66px; max-height: none; object-fit: cover; }

/* Lecteur PDF en page (documents techniques). */
.pdf-modal { position: fixed; inset: 0; z-index: 1000; display: grid; place-items: center; padding: 24px; background: rgba(53, 55, 50, 0.72); }
.pdf-modal[hidden] { display: none; }
.pdf-modal-inner { width: min(1000px, 100%); height: min(90vh, 100%); display: flex; flex-direction: column; background: #fff; border-radius: 12px; overflow: hidden; box-shadow: var(--shadow); }
.pdf-modal-bar { display: flex; align-items: center; gap: 10px; padding: 8px 12px; border-bottom: 1px solid var(--line); }
.pdf-modal-title { font-weight: 800; flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Les pages du PDF, dessinees par pdf.js dans des canvas et empilees verticalement. */
.pdf-modal-pages {
    flex: 1 1 auto; width: 100%; overflow: auto; background: #f4f6f8;
    padding: 12px; display: flex; flex-direction: column; align-items: center; gap: 12px;
}
.pdf-page { max-width: 100%; background: #fff; box-shadow: 0 2px 10px rgba(0, 0, 0, .18); border-radius: 2px; }
.pdf-loading { color: var(--muted); font-size: 12px; padding: 24px; }
.pdf-modal-pageinfo { font-size: 11px; white-space: nowrap; }
body.modal-open { overflow: hidden; }
@media (max-width: 560px) { .pdf-modal { padding: 0; } .pdf-modal-inner { width: 100%; height: 100%; border-radius: 0; } }
.form { display: grid; gap: 18px; }
.movement-panels { display: grid; grid-template-columns: 1fr; gap: 16px; margin-bottom: 18px; }
.movement-panels .compact .form-grid { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.compact .form-grid { grid-template-columns: 2fr 1fr 1fr 2fr; }
.assembly-row { margin-bottom: 10px; }
.assembly-row select { flex: 1 1 360px; }
.assembly-row input { width: 110px; }
/* Lignes de vente (article + quantite + prix + total) */
.sale-lines { display: grid; gap: 8px; margin-bottom: 10px; }
.sale-line { display: grid; grid-template-columns: minmax(0, 1fr) 90px 120px 110px 40px; gap: 8px; align-items: center; }
.sale-line .sale-item { min-width: 0; }
.sale-line input { width: 100%; }
.sale-line-total { font-weight: 700; text-align: right; font-variant-numeric: tabular-nums; }
.sale-del { width: 36px; padding: 4px 0; }
.sale-shipping { margin: 16px 0; padding: 14px; border: 1px solid var(--line); border-radius: 10px; background: #fafbfc; }
.sale-shipping h2 { margin: 0 0 8px; font-size: 14px; }
.ship-badge { display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: 10px; font-weight: 800; white-space: nowrap; }
.ship-delivered { background: #e6f7ec; color: #1c7a3f; }
.ship-transit { background: #fff4e0; color: #9a5b00; }
.ship-none { background: #eef0f3; color: var(--muted); }
.sale-track-live { margin-top: 10px; }
.track-result { margin-bottom: 8px; }
.track-summary { margin: 6px 0; }
.track-timeline { list-style: none; margin: 6px 0 0; padding: 0; display: grid; gap: 4px; }
.track-timeline li { font-size: 12px; padding: 6px 10px; border: 1px solid var(--line); border-radius: 8px; background: #fff; }
.track-timeline .track-date { font-weight: 700; font-variant-numeric: tabular-nums; margin-right: 6px; }
.track-cached { font-size: 10px; margin: 6px 0 0; }
.sale-track-form { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-top: 8px; }
.sale-track-form select { width: auto; flex: 0 1 220px; }
.sale-track-form input { flex: 1 1 200px; }
/* Liste des colis d'une vente (multi-colis). */
.parcel-carrier { margin: 10px 0 6px; font-size: 11px; color: var(--muted); }
.parcel-list { list-style: none; margin: 6px 0 10px; padding: 0; display: grid; gap: 8px; }
.parcel-item { border: 1px solid var(--line); border-radius: 9px; background: #fff; padding: 8px 10px; }
.parcel-head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.parcel-num { font-size: 12px; }
.parcel-del-form { margin: 0 0 0 auto; display: flex; }
.parcel-hint { margin: 6px 0 0; font-size: 11px; }
.parcel-add-form { border-top: 1px dashed var(--line); padding-top: 10px; }
/* Liste des ventes : sous-ligne pleine largeur par colis (N° + etape en cours). */
/* Une vente occupe UNE ligne, plus une ligne par colis. Les lignes de colis
   appartiennent visuellement a la vente au-dessus : pas de trait entre elles. */
.sale-row-head > td { border-bottom: none; }
.parcel-subrow:not(.parcel-subrow-last) > td { border-bottom: none; }
/* Trait FRANC a la fin de chaque vente, pour separer nettement une vente de la
   suivante. Il se pose sur la derniere ligne du bloc : la ligne de colis finale
   s'il y en a, sinon la ligne de la vente elle-meme (qui n'a pas .sale-row-head).
   PORTEE LIMITEE a .sale-table : sans ce prefixe, la regle epaissirait le trait
   de TOUS les tableaux de l'application. */
.sale-table tbody > .parcel-subrow-last > td,
.sale-table tbody > tr:not(.sale-row-head):not(.parcel-subrow) > td {
    border-bottom: 2px solid #b8c1cc;
}
.parcel-subrow > td { padding: 3px 8px; font-size: 11px; line-height: 1.35; background: #fbfcfd; border-bottom: none; }
.parcel-subrow-last > td { border-bottom: 1px solid var(--line); }
.parcel-subrow .parcel-icon { margin-right: 4px; opacity: .7; }
.parcel-subrow .parcel-line-num { font-weight: 700; font-variant-numeric: tabular-nums; }
.parcel-subrow .parcel-line-sep { margin: 0 6px; color: var(--muted); }
.parcel-subrow .parcel-line-status { color: #34404d; }
.parcel-subrow .parcel-line-date { margin-left: 8px; font-size: 10px; white-space: nowrap; }
.sale-foot { display: flex; justify-content: flex-end; margin: 10px 0; font-size: 14px; }
.sale-grand strong { font-size: 16px; }
@media (max-width: 700px) {
    .sale-line { grid-template-columns: 1fr 1fr; grid-auto-rows: auto; }
    .sale-line .sale-item { grid-column: 1 / -1; }
    .sale-line-total { grid-column: 1 / -1; text-align: left; }
}
.flash {
    font-size: 12px;
    margin-bottom: 18px;
    padding: 12px 14px;
    border-radius: 8px;
    background: #eaf7f0;
    color: var(--success);
    font-weight: 700;
}
.flash.error { background: #fdeceb; color: var(--danger); }
.muted { color: var(--muted); }

.item-card[hidden],
.live-empty[hidden] { display: none; }
.live-empty { margin-top: 18px; }

/* Fiche piece : formulaire a gauche, media (3D + photos) a droite */
.part-form-grid-2col { display: grid; grid-template-columns: minmax(0, 1fr) 460px; gap: 24px; align-items: start; }
.part-form-main { display: grid; gap: 18px; }
/* Titre de section place a l'exterieur, au-dessus du cadre (panel). */
.panel-title-out { margin: 0 0 -8px; }
.part-form-side { display: grid; gap: 10px; align-content: start; }
.part-form-side h2 { margin: 4px 0 2px; font-size: 13px; }
/* Vue 3D et modele imprimable (STL) en 2 colonnes fixes : cote a cote quand les deux
   existent, et sur une SEULE moitie (pas toute la largeur) quand il n'y a que la 3D. */
.pfs-3d { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; align-items: start; }
.pfs-3d .pfs-model-col { min-width: 0; }
.model-inline { width: 100%; aspect-ratio: 1 / 1; height: auto; background: #f5f6f8; border-radius: 10px; overflow: hidden; border: 1px solid #e3e7ec; }
/* Outils d'orientation de la vue 3D (fiche piece) */
.part-3d-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 8px; }
.part-3d-tools .p3d-hint { flex: 1 1 100%; font-size: 11px; color: var(--muted); }
.part-3d-tools .p3d-axes { display: flex; gap: 4px; }
.part-3d-tools .p3d-axes .button.small { min-width: 34px; padding: 4px 6px; font-variant-numeric: tabular-nums; }
.part-3d-tools .p3d-status { flex: 1 1 100%; font-size: 11px; font-weight: 700; }
/* Curseurs d'orientation 3D (X/Y/Z de -90 a 90, + zoom) */
#orient-viewer { max-width: 560px; }
.orient-sliders { flex: 1 1 100%; display: grid; gap: 8px; max-width: 560px; }
.orient-slider { display: grid; grid-template-columns: 62px 1fr 56px; align-items: center; gap: 10px; font-weight: 700; color: var(--ink); }
.orient-slider input[type="range"] { width: 100%; min-height: 0; padding: 0; margin: 0; border: 0; background: transparent; border-radius: 0; accent-color: var(--primary); cursor: pointer; }
.orient-slider output { text-align: right; font-variant-numeric: tabular-nums; color: var(--muted); }

/* Editeur d'images (back-office) */
.img-editor .ie-top { display: flex; flex-wrap: wrap; align-items: end; gap: 14px; margin-bottom: 14px; }
.img-editor .ie-pick { flex: 1 1 320px; min-width: 0; }
.img-editor .ie-upload { cursor: pointer; }
.img-editor .ie-main { display: grid; grid-template-columns: 1fr 320px; gap: 18px; align-items: start; }
.img-editor .ie-stage { position: relative; min-height: 240px; background: #f1f2f4; border: 1px solid var(--line); border-radius: 10px; display: grid; place-items: center; overflow: hidden; padding: 8px; }
.img-editor #ie-canvas { max-width: 100%; max-height: 64vh; height: auto; display: block; border-radius: 4px; touch-action: none;
    /* Damier visible a travers les zones rendues transparentes (detourage). */
    background-image: linear-gradient(45deg, #ccc 25%, transparent 25%), linear-gradient(-45deg, #ccc 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #ccc 75%), linear-gradient(-45deg, transparent 75%, #ccc 75%);
    background-size: 18px 18px; background-position: 0 0, 0 9px, 9px -9px, -9px 0; background-color: #fff; }
.img-editor.ie-bg-mode #ie-canvas { outline: 2px solid var(--primary); }
.img-editor .ie-empty { color: var(--muted); font-weight: 700; }
.img-editor .ie-crop { position: absolute; border: 1.5px dashed #fff; outline: 1px solid rgba(0,0,0,.5); background: rgba(60,120,240,.12); pointer-events: none; }
.img-editor .ie-tools { display: grid; gap: 14px; }
.img-editor .ie-group { display: grid; gap: 8px; padding: 12px; border: 1px solid var(--line); border-radius: 10px; background: #fff; }
.img-editor .ie-group h3 { margin: 0 0 2px; font-size: 12px; }
.img-editor .ie-btns { display: flex; flex-wrap: wrap; gap: 6px; }
.img-editor .ie-slider { display: grid; grid-template-columns: 88px 1fr 52px; align-items: center; gap: 10px; font-size: 11px; font-weight: 700; color: var(--ink); }
.img-editor .ie-slider input[type="range"] { width: 100%; min-height: 0; padding: 0; margin: 0; border: 0; background: transparent; border-radius: 0; accent-color: var(--primary); cursor: pointer; }
.img-editor .ie-slider output { text-align: right; font-variant-numeric: tabular-nums; color: var(--muted); }
.img-editor .ie-check { display: flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 700; }
.img-editor .ie-actions { display: grid; gap: 8px; }
.img-editor .ie-status { font-size: 11px; font-weight: 700; }
@media (max-width: 860px) { .img-editor .ie-main { grid-template-columns: 1fr; } }
.photo-gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(92px, 1fr)); gap: 10px; }
.photo-thumb { margin: 0; position: relative; }
.photo-thumb img { width: 100%; aspect-ratio: 1 / 1; height: auto; object-fit: cover; border-radius: 8px; display: block; border: 1px solid #e3e7ec; }
/* Liseré orange (1 px) autour des zones vue 3D / vue éclatée.
   La PHOTO n'a plus de liseré : la carte porte deja le sien, et la photo est
   simplement separee du corps par une ligne (voir .media-photo ci-dessous). */
.model-inline,
.model-modal-viewer,
.photo-thumb img,
.row-thumb,
.exploded-editor img,
.exploded-view img,
.ev-ref-preview img { border: 1px solid var(--primary) !important; }
/* Petite croix de suppression posee sur la photo elle-meme. */
.photo-del-btn {
    position: absolute;
    top: 4px;
    right: 4px;
    width: 22px;
    height: 22px;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    border-radius: 50%;
    background: var(--danger);
    color: #fff;
    font-size: 14px;
    line-height: 1;
    font-weight: 700;
    cursor: pointer;
    transition: background 0.15s ease;
}
.photo-del-btn:hover { background: #8e1b12; }

/* Ligne de fichier (STEP / STL) : nom + actions telecharger/supprimer. */
.file-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 7px 9px;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: #fff;
}
.file-meta { display: grid; min-width: 0; }
.file-kind { font-size: 9px; font-weight: 700; letter-spacing: 0.04em; color: var(--muted); text-transform: uppercase; }
.file-name { font-size: 11px; font-weight: 700; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.file-row-actions { display: flex; gap: 6px; flex: none; }
.button.small { min-height: 28px; padding: 4px 9px; font-size: 11px; }
/* Boutons de suppression : rouge plein, texte/croix blanche. */
.button.danger { background: var(--danger); color: #fff; border-color: var(--danger); }
.button.danger:hover { background: #8e1b12; border-color: #8e1b12; color: #fff; }
/* Action de RATTRAPAGE (ex. annuler une reception) : verdatre. Ni rouge — elle ne
   detruit rien —, ni orange — ce n'est pas l'action principale de la ligne. */
/* Action de confirmation (« Enregistrer les modifications ») : vert plein, sur le
   modele de .primary. Distinct du .soft-green pale qui sert aux actions mineures. */
.button.success { border-color: var(--success); background: var(--success); color: #fff; }
.button.success:hover { border-color: #12603e; background: #12603e; color: #fff; }
/* Bouton BROUILLON : orange ambre. Volontairement plus sombre que --primary
   (#FF6B26), sinon il se confondrait avec les boutons d'action principaux. Reprend
   le ton du statut « En maintenance » et du badge Brouillon — meme famille visuelle
   pour la meme notion : un travail commence, pas encore termine. */
.button.draft { border-color: #b06a00; background: #d98613; color: #fff; }
.button.draft:hover { border-color: #8a5300; background: #b06a00; color: #fff; }
.page-header .button.draft { box-shadow: 0 2px 7px rgba(176, 106, 0, .32); }
.page-header .button.draft:hover { box-shadow: 0 3px 11px rgba(176, 106, 0, .42); }
.page-header .button.success { box-shadow: 0 2px 7px rgba(25, 122, 80, .32); }
.page-header .button.success:hover { box-shadow: 0 3px 11px rgba(25, 122, 80, .42); }
.button.soft-green { background: #e6f7ec; color: #14663a; border-color: #9ed4b5; }
.button.soft-green:hover { background: #d3f0e0; border-color: #6fbf95; color: #0f4f2c; }
/* Bouton supprimer compact : juste une croix blanche (cartes). */
.icon-del { flex: 0 0 auto !important; width: auto !important; min-width: 30px; padding-left: 8px; padding-right: 8px; font-size: 14px; line-height: 1; font-weight: 800; }

/* Menu mobile : bouton hamburger + voile, masques sur grand ecran. */
.nav-burger { display: none; }
.nav-backdrop { display: none; }
.mobile-logo { display: none; }
.nav-burger {
    position: fixed; top: 12px; left: 12px; z-index: 95;
    width: 44px; height: 44px; padding: 11px 10px; gap: 4px;
    flex-direction: column; justify-content: center; align-items: stretch;
    background: #fff; border: 1px solid var(--line); border-radius: 10px;
    box-shadow: var(--shadow); cursor: pointer;
}
.nav-burger span { display: block; height: 2.5px; border-radius: 2px; background: var(--ink); transition: transform .2s, opacity .2s; }
.nav-backdrop { position: fixed; inset: 0; z-index: 88; background: rgba(15, 23, 32, 0.45); }

@media (max-width: 860px) {
    .nav-burger { display: flex; }
    .sidebar {
        position: fixed; inset: 0 auto 0 0; width: 100%; height: 100vh;
        padding-top: 60px;
        transform: translateX(-100%); transition: transform .25s ease;
        z-index: 90; overflow-y: auto; box-shadow: 0 0 40px rgba(0, 0, 0, 0.28);
    }
    .nav-toggle-check:checked ~ .sidebar { transform: translateX(0); }
    .nav-toggle-check:checked ~ .nav-backdrop { display: block; }
    /* Hamburger -> croix quand le menu est ouvert. */
    .nav-toggle-check:checked ~ .nav-burger span:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
    .nav-toggle-check:checked ~ .nav-burger span:nth-child(2) { opacity: 0; }
    .nav-toggle-check:checked ~ .nav-burger span:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }
    nav { grid-template-columns: 1fr; }
    .main { margin-left: 0; padding: 22px; padding-top: 60px; }
    /* En-tete compacte : reste sur une ligne (avec retour si besoin), moins haute. */
    .page-header { flex-wrap: wrap; align-items: center; gap: 8px 12px; margin-bottom: 26px; }
    .page-header h1 { font-size: 16px; }
    .page-header .eyebrow { font-size: 9px; }
    .page-header .button { min-height: 30px; padding: 5px 10px; }
    .search { align-items: stretch; flex-direction: column; }
    .stats, .form-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .part-form-grid-2col { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    /* Modif piece : Vue 3D a gauche, Photos a droite (au lieu d'empiler). */
    .part-form-side:has(.pfs-photos) { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; align-items: start; }
    .part-form-side .pfs-3d, .part-form-side .pfs-photos { min-width: 0; }
    /* Logo : retire du menu (tiroir), affiche plus petit en haut de page. */
    .sidebar .brand { display: none; }
    .mobile-logo { display: block; position: fixed; top: 12px; left: 50%; transform: translateX(-50%); height: 34px; width: auto; z-index: 80; }
    /* Pas d'ajout / de modification / de telechargement 3D sur mobile,
       SAUF la modification des vues eclatees (autorisee sur mobile). */
    a.button[href*="_form"]:not([href*="exploded_view_form"]):not([href*="sale_form"]) { display: none !important; }
    .file-link, .file-row a[download] { display: none !important; }
}

/* Tablette paysage / fenetre etroite (menu fixe encore present a 260px) :
   on empile les colonnes larges pour qu'elles ne se serrent pas contre le menu
   et ne debordent pas a droite. */
@media (min-width: 861px) and (max-width: 1180px) {
    .main { padding: 28px 22px; }
    .part-form-grid-2col { grid-template-columns: minmax(0, 1fr); }
    .compact .form-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Smartphone : TOUT sur une seule colonne (formulaires, grilles, vues 3D/STL). */
@media (max-width: 640px) {
    .stats,
    .form-grid,
    .part-form-grid-2col,
    .part-form-side,
    .part-form-side:has(.pfs-photos),
    .pfs-3d,
    .dash-grid { grid-template-columns: 1fr !important; }
}

/* Fiche pièce : Informations et Vue 3D en 2 panneaux de HAUTEUR EGALE, titres alignes.
   Le cadre Informations s'etire pour atteindre la hauteur de la vue 3D (carree).
   Smartphone (<= 640 px) : une seule colonne. */
.part-view-top { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 400px); gap: 24px; align-items: stretch; margin-bottom: 18px; }
.pv-pane { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.pv-pane .panel-title-out { margin: 0; }
.pv-pane > .panel { flex: 1 1 auto; }
/* 2e ligne : Description (gauche) et Graphe (droite, largeur de la vue 3D), titres
   au-dessus de leur cadre, et les DEUX cadres a la meme hauteur. */
.part-view-row2 { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 400px); gap: 24px; align-items: stretch; }
.part-view-row2 .stock-trend-panel { margin-top: 0; }
/* Une seule colonne quand la 2e est vide (piece sans vue 3D, ou sans description) :
   sinon la grille reserve ses 400px pour rien et le contenu s'arrete au milieu. */
.part-view-top.pv-single, .part-view-row2.pv-single { grid-template-columns: minmax(0, 1fr); }
@media (max-width: 640px) { .part-view-top, .part-view-row2 { grid-template-columns: 1fr; } }




/* ===== Mouvements groupes par date + pop-up ===== */
.movement-groups { display: grid; gap: 8px; }
.movement-group {
    display: flex; align-items: center; gap: 12px; width: 100%;
    padding: 12px 14px; border: 1px solid var(--line); border-radius: 10px;
    background: #fff; cursor: pointer; text-align: left; font-family: inherit;
}
.movement-group:hover { border-color: var(--primary); background: var(--primary-soft); }
.mg-date { font-weight: 800; font-size: 13px; min-width: 96px; }
.mg-summary { color: var(--muted); font-size: 12px; }
.mg-arrow { margin-left: auto; color: var(--muted); display: grid; place-items: center; }
.mg-arrow svg { width: 18px; height: 18px; }

.movement-modal {
    position: fixed; inset: 0; z-index: 60;
    display: grid; place-items: center;
    background: rgba(20, 23, 28, 0.55); padding: 20px;
}
.movement-modal[hidden] { display: none; }
.movement-modal-inner {
    position: relative;
    width: min(660px, 100%); max-height: 86vh; overflow: auto;
    background: #fff; border-radius: 14px; padding: 20px;
}
.movement-modal-inner h3 { margin: 0 0 14px; padding-right: 80px; }
.movement-modal-close { position: absolute; top: 12px; right: 12px; }
.movement-modal table { width: 100%; }
.movement-modal td form { display: inline; }
.movement-modal > .movement-modal-inner > form[action*="delete_movement_group"] { margin-top: 14px; display: block; }

/* Mouvements : ligne (groupe + bouton supprimer) */
.movement-group-row { display: flex; align-items: stretch; gap: 8px; }
.movement-group-row .movement-group { flex: 1 1 auto; }
.mg-delete-form { display: flex; }
.mg-delete-form .button { white-space: nowrap; }

/* Pop-up : lignes serrees, texte plus petit */
.movement-lines { font-size: 11px; }
.movement-lines th, .movement-lines td { padding: 3px 8px; line-height: 1.25; }
.movement-lines thead th { font-size: 10px; }
.mvt-tag { display: inline-block; padding: 1px 7px; border-radius: 999px; font-size: 9.5px; font-weight: 800; }
.mvt-user { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.mvt-user .presence-badge { width: 22px; height: 22px; font-size: 8.5px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.18); }
.mvt-user-name { font-size: 10.5px; }
.mvt-in { background: #e4f6ea; color: #1c7a3f; }
.mvt-out { background: #fdeaea; color: var(--danger); }
/* Inventaire = un CONSTAT, pas un flux : couleur neutre, surtout pas le rouge des
   sorties (un recomptage n'est pas un retrait de stock). */
.mvt-adjust { background: #e8eef4; color: #2d5c8a; }

/* Petit bouton ovale rouge avec croix blanche */
.mvt-del-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 26px; height: 18px; padding: 0;
    border: none; border-radius: 999px;
    background: var(--danger); color: #fff;
    font-size: 13px; line-height: 1; cursor: pointer;
}
.mvt-del-btn:hover { background: #8c1b13; }

/* Zoom image (lightbox) */
.zoomable { cursor: zoom-in; }
.image-lightbox { position: fixed; inset: 0; z-index: 80; display: grid; place-items: center; background: rgba(10, 12, 15, 0.85); padding: 24px; cursor: zoom-out; }
.image-lightbox[hidden] { display: none; }
.image-lightbox img { max-width: 96vw; max-height: 92vh; border-radius: 8px; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5); }

/* Fiche piece (vue) */
.detail-list { display: grid; gap: 10px; margin: 0 0 8px; }
.detail-list > div { display: grid; grid-template-columns: minmax(150px, 230px) 1fr; gap: 12px; }
.detail-list dt { color: var(--muted); font-weight: 700; font-size: 11px; }
.detail-list dd { margin: 0; font-weight: 600; }
.detail-description { white-space: pre-line; }

/* Carte piece cliquable */
.item-card-link { display: block; }
.item-card-title { color: inherit; text-decoration: none; }
.item-card-title:hover { color: var(--primary-dark); }

/* En-tete : actions + bascule de vue cartes/liste */
.header-actions { display: flex; align-items: center; gap: 12px; }
.view-toggle { display: inline-flex; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; background: #fff; }
.view-toggle-btn { display: grid; place-items: center; width: 36px; height: 34px; border: none; background: #fff; color: var(--muted); cursor: pointer; }
.view-toggle-btn + .view-toggle-btn { border-left: 1px solid var(--line); }
.view-toggle-btn svg { width: 17px; height: 17px; }
.view-toggle-btn.active { background: var(--primary); color: #fff; }
.card-supplier { color: var(--muted); font-size: 11px; margin-top: 2px; }
.card-cost { color: var(--primary-dark); font-size: 11.5px; margin-top: 2px; }

/* Vue liste : cartes en lignes horizontales */
.grid-list.list-view { grid-template-columns: 1fr; }
.grid-list.list-view .item-card { display: grid; grid-template-columns: 108px 1fr; align-items: stretch; }
.grid-list.list-view .item-card .media-stack,
.grid-list.list-view .item-card .media-photo { height: 100%; }
.grid-list.list-view .item-card .part-photo,
.grid-list.list-view .item-card .empty-media { height: 100%; min-height: 104px; }

/* Vignette de ligne (listes) */
.row-thumb { width: 40px; height: 40px; object-fit: cover; border-radius: 6px; display: block; border: 1px solid #e3e7ec; }
.row-thumb-empty { background: #e8eef2; }

/* Liste des pieces d'un assemblage avec vignette a gauche */
.assembly-part-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.assembly-part-list li { display: flex; align-items: center; gap: 12px; padding: 7px 8px; border: 1px solid var(--line); border-radius: 10px; }
.apl-thumb { flex: none; display: block; }
.apl-info { display: grid; min-width: 0; flex: 1 1 auto; }
.apl-name { font-weight: 700; color: var(--ink); text-decoration: none; }
.apl-name:hover { color: var(--primary-dark); }
.apl-sku { color: var(--muted); font-size: 11px; }
.apl-qty { font-weight: 800; font-size: 13px; }
.apl-stock { color: var(--muted); font-size: 11px; min-width: 70px; text-align: right; }

/* Barre de menu rapide en haut (ecrans larges) */
.topbar { display: none; }
@media (min-width: 1100px) {
    .topbar { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 18px; padding: 8px; background: #fff; border: 1px solid var(--line); border-radius: 12px; }
}
.topbar-item { display: inline-flex; align-items: center; gap: 9px; padding: 6px 12px; border-radius: 9px; text-decoration: none; color: var(--ink); font-weight: 700; font-size: 12px; }
.topbar-item:hover { background: #f0f1f3; }
.topbar-item.active { background: var(--primary-soft); }
.topbar-icon { display: grid; place-items: center; width: 32px; height: 32px; border-radius: 8px; background: #f1f2f4; }
.topbar-icon svg { width: 18px; height: 18px; }

/* Barre de statistiques en haut (toujours visible, remplace le menu rapide) */
.topbar-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(112px, 1fr)); gap: 8px; margin-bottom: 14px; padding: 8px; background: #fff; border: 1px solid var(--line); border-radius: 12px; }
.topstat { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; padding: 8px 7px; border-radius: 10px; background: #f7f8fa; text-decoration: none; color: var(--ink); text-align: center; }
a.topstat:hover { background: var(--primary-soft); }
.topstat-label { color: var(--muted); font-weight: 700; font-size: 11px; text-transform: uppercase; letter-spacing: .3px; }
.topstat-value { font-size: 15px; font-weight: 800; line-height: 1; }
.topstat-alert.has-alerts { background: #fdecea; }
.topstat-alert.has-alerts .topstat-value { color: var(--danger); }

/* Repartition du stock par emplacement (formulaires piece / assemblage) */
.field-full { grid-column: 1 / -1; }
.loc-stock-field { display: flex; flex-direction: column; gap: 8px; }
.loc-stock-title { font-weight: 700; font-size: 13px; }
.loc-dist-table { width: 100%; border-collapse: collapse; }
.loc-dist-table th { text-align: left; font-size: 11px; color: var(--muted); font-weight: 700; padding: 4px 8px; }
.loc-dist-table td { padding: 4px 8px; border-top: 1px solid var(--line); vertical-align: middle; }
.loc-dist-table th:last-child, .loc-dist-table td:last-child { width: 36px; text-align: center; }
.loc-dist-name { font-weight: 600; font-size: 12px; }
.loc-dist-table input[data-loc-qty] { width: 90px; text-align: center; }
.loc-row-del { border: none; background: var(--danger); color: #fff; width: 26px; height: 26px; border-radius: 7px; font-size: 15px; line-height: 1; cursor: pointer; }
.loc-row-del:hover { background: #8e1b12; }
.loc-add { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.loc-add select { max-width: 260px; }
.loc-save-status { font-size: 11px; font-weight: 700; }
.loc-save-status.ok { color: #1c7a3f; }
.loc-save-status.err { color: var(--danger); }
/* Total du stock dans la fiche : plus gros, gras, vert si en stock / rouge si rupture. */
.loc-total-num { font-weight: 800; font-size: 1.35em; }
.loc-total-num.ok { color: #1c7a3f; }
.loc-total-num.out { color: var(--danger); }

/* Rendu du manuel (Markdown) */
.markdown-body { line-height: 1.6; }
.markdown-body h1, .markdown-body h2, .markdown-body h3 { margin: 1.2em 0 .5em; line-height: 1.25; }
.markdown-body h1 { font-size: 24px; }
.markdown-body h2 { font-size: 19px; border-bottom: 1px solid var(--line); padding-bottom: 6px; }
.markdown-body h3 { font-size: 15px; }
.markdown-body h4 { font-size: 14px; margin: 1em 0 .4em; }
.markdown-body p { margin: .6em 0; }
.markdown-body ul, .markdown-body ol { margin: .6em 0 .6em 1.4em; }
.markdown-body li { margin: .25em 0; }
.markdown-body code { background: #f1f2f4; padding: 1px 5px; border-radius: 5px; font-size: 12px; }
.markdown-body pre { background: #1e1f24; color: #f4f4f5; padding: 14px 16px; border-radius: 10px; overflow: auto; }
.markdown-body pre code { background: none; color: inherit; padding: 0; }
.markdown-body blockquote { margin: .8em 0; padding: 6px 14px; border-left: 3px solid var(--primary); background: var(--primary-soft); border-radius: 0 8px 8px 0; }
.markdown-body hr { border: none; border-top: 1px solid var(--line); margin: 1.6em 0; }
.markdown-body table { width: 100%; border-collapse: collapse; }
.markdown-body th, .markdown-body td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--line); }
.markdown-body a { color: var(--primary); }

/* Indicateur d'auto-enregistrement */
.autosave-status { font-size: 12px; font-weight: 700; align-self: center; }
.autosave-status.ok { color: #1c7a3f; }
.autosave-status.err { color: var(--danger); }
.inv-status { font-size: 11px; font-weight: 700; white-space: nowrap; }
.inv-status.ok { color: #1c7a3f; }
.inv-status.err { color: var(--danger); }

/* Zones de glisser-deposer : icone nuage + texte court, sans bouton de fichier natif */
.dropzone {
    display: flex;
    align-items: center;
    gap: 10px;
    border: 1.5px dashed #c2c9d2;
    border-radius: 10px;
    padding: 8px 12px;
    background: #f6f7f8;
    cursor: pointer;
    text-align: left;
    transition: border-color 0.15s ease, background 0.15s ease;
}
.dropzone:hover { border-color: var(--primary); }
.dropzone.dragover { border-color: var(--primary); background: var(--primary-soft); }
/* L'input fichier natif (bouton "Choisir un fichier") est masque ; le label reste cliquable */
.dropzone input[type=file] { position: absolute; width: 1px; height: 1px; min-height: 0; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); border: 0; }
.dropzone-icon { width: 26px; height: 26px; flex: 0 0 auto; color: var(--muted); }
.dropzone-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.dropzone-title { font-size: 12px; font-weight: 700; color: var(--ink); line-height: 1.2; }
.dropzone-formats { font-size: 10px; font-weight: 600; color: var(--muted); }
/* Nom du fichier deja charge / nouvellement selectionne. */
.dropzone-current { font-size: 10.5px; font-weight: 700; color: #1c7a3f; line-height: 1.25; word-break: break-word; }
.dropzone-current:empty { display: none; }
.dropzone-current-label { font-weight: 800; }
.dropzone.is-loaded { border-style: solid; border-color: #9cc8ad; background: #f1faf4; }
.dropzone.is-loaded .dropzone-icon { color: #1c7a3f; }

/* Vue liste compacte : ligne 2x moins haute, champs etales sur la largeur */
.grid-list.list-view .item-card { grid-template-columns: 72px 1fr; align-items: center; }
.grid-list.list-view .item-card .part-photo,
.grid-list.list-view .item-card .empty-media { height: 66px; min-height: 66px; font-size: 0; }
.grid-list.list-view .item-card .media-view-buttons { display: none; }
/* Vue liste : pastille de stock centree en haut de la photo (colonne 72px). */
.grid-list.list-view .item-card .stock-pill { right: auto; left: 36px; transform: translateX(-50%); top: 4px; padding: 1px 6px; font-size: 9px; }
.grid-list.list-view .item-body { display: flex; align-items: center; gap: 14px; padding: 6px 12px; flex-wrap: nowrap; }
.grid-list.list-view .item-body > * { margin: 0; }
.grid-list.list-view .item-body .sku { flex: 0 0 auto; }
.grid-list.list-view .item-body h2 { font-size: 13px; flex: 0 0 auto; white-space: nowrap; }
.grid-list.list-view .item-body > p { color: var(--muted); font-size: 11px; flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.grid-list.list-view .item-body .card-supplier { flex: 0 1 auto; }
.grid-list.list-view .item-body .stock-line { flex: 0 0 auto; gap: 14px; margin-left: auto; }
.grid-list.list-view .item-body .actions { flex: 0 0 auto; }

/* Liste des assemblages : rangee compacte sur UNE ligne -> photo + reference +
   nom + stock seulement (on masque les badges, le prix de revient et la courbe). */
#assemblies-grid.list-view .item-body > p,
#assemblies-grid.list-view .item-body .card-cost,
#assemblies-grid.list-view .item-body .buildable-badge,
#assemblies-grid.list-view .item-body .shortage-badge,
#assemblies-grid.list-view .item-body .card-buildable,
#assemblies-grid.list-view .item-body .card-spark { display: none; }

/* Fiche piece : champs plus compacts */
.part-form-main .form-grid { gap: 10px; }
.part-form-main .form-grid label { font-size: 10px; gap: 4px; }
.part-form-main .form-grid input,
.part-form-main .form-grid select { min-height: 30px; padding: 5px 8px; font-size: 11.5px; }
.part-form-main > label textarea { min-height: 30px; }

/* ===== Vue eclatee : pastilles (hotspots) ===== */
.exploded-editor, .exploded-view { position: relative; display: inline-block; max-width: 100%; line-height: 0; }
.exploded-editor { cursor: crosshair; }
.exploded-editor img, .exploded-view img { max-width: 100%; height: auto; display: block; border-radius: 8px; }
.hotspot { position: absolute; transform: translate(-50%, -50%); line-height: normal; cursor: grab; touch-action: none; }
.hotspot:active { cursor: grabbing; }
/* Menu contextuel des pastilles (clic droit) */
.hotspot-menu { position: fixed; z-index: 1200; min-width: 150px; background: #fff; border: 1px solid var(--line); border-radius: 10px; box-shadow: var(--shadow); padding: 5px; display: grid; gap: 2px; }
.hotspot-menu-item { display: block; width: 100%; text-align: left; background: none; border: 0; border-radius: 7px; padding: 8px 12px; font-size: 12px; font-weight: 700; color: var(--ink); cursor: pointer; min-height: 0; }
.hotspot-menu-item:hover { background: var(--primary-soft); }
.hotspot-menu-item.danger { color: var(--danger); }
.hotspot-menu-item.danger:hover { background: #fdecec; }
.hotspot-dot {
    display: grid; place-items: center; width: 24px; height: 24px;
    border-radius: 50%; background: var(--primary); color: #fff;
    font-size: 11px; font-weight: 800; border: 2px solid #fff;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.45); cursor: pointer;
}
.hotspot-label {
    position: absolute; left: 50%; bottom: calc(100% + 6px); transform: translateX(-50%);
    white-space: nowrap; background: #14171c; color: #fff; padding: 4px 8px;
    border-radius: 6px; font-size: 11px; font-weight: 700; opacity: 0;
    pointer-events: none; transition: opacity 0.12s ease; display: flex; align-items: center; gap: 6px; z-index: 5;
}
.hotspot:hover .hotspot-label,
.hotspot.show .hotspot-label { opacity: 1; pointer-events: auto; }
.hotspot-link { text-decoration: none; }
.hotspot-del {
    border: none; background: rgba(255, 255, 255, 0.25); color: #fff;
    border-radius: 50%; width: 16px; height: 16px; cursor: pointer;
    font-size: 11px; line-height: 1; padding: 0;
}
.hotspot-del:hover { background: var(--danger); }
/* Dans l'editeur 2D : croix de suppression TOUJOURS visible au coin de la pastille
   (indispensable sur tablette, ou le clic droit n'existe pas). */
.exploded-editor .hotspot-del {
    position: absolute; top: -9px; right: -9px; z-index: 6;
    width: 19px; height: 19px; background: var(--danger); color: #fff;
    border: 2px solid #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
    display: grid; place-items: center; font-weight: 800;
}

/* ===== Editeur / visualiseur de vue eclatee 3D =====
   Le rendu utilise le conteneur .model-inline eprouve (meme que les fiches
   pieces) ; .exploded-3d-wrap ne fait qu'ajouter la couche d'etiquettes. */
.exploded-3d-wrap { position: relative; width: 100%; }
.exploded-3d-wrap .model-inline { aspect-ratio: 1 / 1; height: auto; }
.exploded-3d-wrap[data-edit] .model-inline { cursor: crosshair; }
/* Bloc du haut : formulaire a gauche, apercu de l'image de reference a droite. */
.ev-form-layout { display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; }
.ev-form-layout > .form { flex: 1 1 460px; }
.ev-ref-preview { flex: 0 0 220px; max-width: 240px; align-self: stretch; }
.ev-ref-preview h3 { margin: 0 0 8px; font-size: 12px; }
.ev-ref-preview img { width: 100%; height: auto; border-radius: 8px; border: 1px solid var(--line); background: #f6f6f6; display: block; }
/* Vue 3D a gauche + liste des elements separes (noeuds GLB) a droite. */
.ex3d-layout { display: flex; gap: 14px; align-items: stretch; flex-wrap: wrap; }
.ex3d-layout > .exploded-3d-wrap { flex: 1 1 360px; }
.ex3d-nodes-panel { flex: 0 0 240px; max-width: 280px; display: flex; flex-direction: column; min-height: 0; }
.ex3d-nodes-panel h3 { margin: 0 0 8px; font-size: 12px; display: flex; align-items: center; gap: 6px; }
.ex3d-nodes-count { background: var(--primary); color: #fff; border-radius: 999px; font-size: 10px; font-weight: 800; padding: 1px 7px; }
.ex3d-nodes-filter { width: 100%; margin-bottom: 8px; padding: 5px 8px; border: 1px solid var(--line); border-radius: 7px; font-size: 11.5px; }
.ex3d-nodes { margin: 0; padding: 0; overflow-y: auto; max-height: 460px; border: 1px solid var(--line); border-radius: 8px; }
/* Groupe = noeud principal (summary) + ses sous-elements (liste repliable). */
.ex3d-node-group { border-bottom: 1px solid #e3e3e3; }
.ex3d-node-group:last-child { border-bottom: none; }
.ex3d-node-main { display: flex; align-items: center; gap: 6px; padding: 7px 9px; cursor: pointer;
    font-size: 11.5px; font-weight: 700; color: var(--ink); background: #f3f4f6; list-style: none; user-select: none; }
.ex3d-node-main::-webkit-details-marker { display: none; }
.ex3d-node-main::before { content: '▸'; flex: 0 0 auto; color: var(--muted); font-size: 9px; transition: transform .12s; }
.ex3d-node-group[open] > .ex3d-node-main::before { transform: rotate(90deg); }
.ex3d-node-main-name { flex: 1 1 auto; word-break: break-word; line-height: 1.25; }
.ex3d-node-main:hover { background: #fff3e0; }
.ex3d-node-main.active { background: #ff6b26; color: #fff; }
.ex3d-node-main.active::before { color: #fff; }
.ex3d-group-count { flex: 0 0 auto; background: #d8dbe0; color: #333; border-radius: 999px; font-size: 9.5px; font-weight: 800; padding: 1px 6px; }
.ex3d-node-main.active .ex3d-group-count { background: rgba(255, 255, 255, .35); color: #fff; }
.ex3d-node-sublist { list-style: none; margin: 0; padding: 0; background: #fff; }
.ex3d-node-sublist li { border-top: 1px solid #f0f0f0; }
.ex3d-node-item { display: block; width: 100%; text-align: left; background: none; border: none; padding: 5px 9px 5px 22px;
    font-size: 10.5px; color: #555; cursor: pointer; word-break: break-word; line-height: 1.3; }
.ex3d-node-item:hover { background: #fff3e0; }
.ex3d-node-item.active { background: #ff6b26; color: #fff; font-weight: 700; }
/* Panneau "Pastilles posees" : numero (devant) + nom de la piece associee. */
.ex3d-placed { margin-top: 14px; }
.ex3d-placed h3 { margin: 0 0 8px; font-size: 12px; display: flex; align-items: center; gap: 6px; }
.ex3d-placed-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
.ex3d-placed-item { display: flex; align-items: center; gap: 8px; padding: 4px 6px; border: 1px solid var(--line); border-radius: 7px; background: #fff; }
.ex3d-placed-num { flex: 0 0 auto; display: grid; place-items: center; width: 20px; height: 20px; border-radius: 50%;
    background: var(--primary); color: #fff; font-size: 10px; font-weight: 800; }
.ex3d-placed-name { font-size: 11px; color: var(--ink); word-break: break-word; line-height: 1.25; }
.ex3d-placed-empty { font-size: 10.5px; }
/* Pastille en cours de deplacement : suit le curseur, mise en avant. */
.ex3d-label.ex3d-dragging { z-index: 8; }
.ex3d-label.ex3d-dragging .ex3d-dot { cursor: grabbing; box-shadow: 0 0 0 3px rgba(255, 107, 38, .5), 0 2px 8px rgba(0, 0, 0, .5); }
.exploded-3d-labels { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
/* Boutons des 6 angles de vue, en surimpression en haut du modele 3D. */
.ex3d-views { position: absolute; top: 8px; left: 50%; transform: translateX(-50%); z-index: 6;
    display: flex; gap: 4px; flex-wrap: wrap; justify-content: center;
    background: rgba(255, 255, 255, 0.85); padding: 4px; border-radius: 9px; box-shadow: 0 1px 5px rgba(0, 0, 0, 0.14); }
.ex3d-views .button { padding: 3px 9px; font-size: 10.5px; min-height: 0; }
.ex3d-label { position: absolute; top: 0; left: 0; pointer-events: auto; text-decoration: none; will-change: transform; transition: opacity 0.12s ease; }
/* Pastille cachee derriere le modele : grisee et semi-transparente. */
.ex3d-label.ex3d-occluded { opacity: 0.32; }
.ex3d-label.ex3d-occluded .ex3d-dot { background: #9aa3ad; box-shadow: none; }
.ex3d-dot {
    display: grid; place-items: center; width: 22px; height: 22px; border-radius: 50%;
    background: var(--primary); color: #fff; font-size: 10px; font-weight: 800;
    border: 2px solid #fff; box-shadow: 0 1px 4px rgba(0, 0, 0, 0.45); cursor: pointer;
}
.exploded-3d-wrap[data-edit] .ex3d-dot { cursor: grab; touch-action: none; }
.exploded-3d-wrap[data-edit] .ex3d-dot:active { cursor: grabbing; }
.ex3d-text {
    position: absolute; left: 50%; bottom: calc(100% + 5px); transform: translateX(-50%);
    white-space: nowrap; background: #14171c; color: #fff; padding: 3px 7px; border-radius: 6px;
    font-size: 10.5px; font-weight: 700; opacity: 0; transition: opacity 0.12s ease;
    display: flex; align-items: center; gap: 6px; z-index: 5;
    text-decoration: none; pointer-events: none; /* cliquable seulement une fois revele */
}
a.ex3d-text { cursor: pointer; }
.ex3d-dot-toggle { font-family: inherit; }
.ex3d-label:hover .ex3d-text,
.ex3d-label.show .ex3d-text { opacity: 1; pointer-events: auto; }
/* Badge de suppression : toujours visible et cliquable en mode edition, en haut a
   droite de la pastille (independant de l'infobulle). */
.ex3d-del { position: absolute; top: -8px; left: calc(50% + 7px); z-index: 6;
    border: 2px solid #fff; background: var(--danger, #dc2626); color: #fff; border-radius: 50%;
    width: 17px; height: 17px; cursor: pointer; font-size: 11px; line-height: 1; padding: 0;
    display: grid; place-items: center; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4); pointer-events: auto; }
.ex3d-del:hover { background: #a30f0f; transform: scale(1.12); }

/* Legende des reperes sous la vue eclatee. */
.hotspot-legend { list-style: none; margin: 8px 0 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 6px; }
.hotspot-legend-item { display: flex; align-items: center; gap: 10px; padding: 5px 8px; border: 1px solid var(--line); border-radius: 8px; text-decoration: none; color: inherit; }
.hotspot-legend-item:hover { border-color: var(--primary); background: var(--primary-soft); }
.hotspot-legend .legend-dot { position: static; transform: none; flex: 0 0 auto; }
.hotspot-legend .legend-name { font-size: 12px; font-weight: 600; }

/* Le filtre live masque les cartes via [hidden] : en vue liste, display:grid
   ecraserait ce masquage -> on le retablit explicitement. */
.grid-list.list-view .item-card[hidden] { display: none; }

/* Back-office : tuiles + table d'inventaire */
.bo-tile { text-decoration: none; color: inherit; }
.bo-tile:hover { border-color: var(--primary); }
.bo-tile .item-body { display: grid; gap: 6px; padding: 18px; }
.bo-tile-icon { display: grid; place-items: center; width: 40px; height: 40px; border-radius: 10px; background: var(--primary-soft); color: var(--primary-dark); }
.bo-tile-icon svg { width: 22px; height: 22px; }
/* Cartes du back-office plus petites : grille plus dense + contenu compact. */
.bo-grid { grid-template-columns: repeat(auto-fill, minmax(168px, 1fr)); gap: 9px; }
.bo-grid .bo-tile .item-body { padding: 12px 10px; gap: 4px; }
/* Icones plus grosses et bien identifiables (cartes restent compactes). */
.bo-grid .bo-tile-icon { width: 48px; height: 48px; border-radius: 12px; }
.bo-grid .bo-tile-icon svg { width: 30px; height: 30px; }
.bo-grid .bo-tile h2 { font-size: 12px; margin: 3px 0 0; }
.bo-grid .bo-tile p { font-size: 10px; line-height: 1.3; }
.inventory-table input { max-width: 110px; min-height: 30px; padding: 5px 8px; }
.inventory-table td { vertical-align: middle; }

/* ===== Manuel : sommaire cliquable + mise en page 2 colonnes ===== */
html { scroll-behavior: smooth; }
.manual-layout { display: grid; grid-template-columns: 250px 1fr; gap: 16px; align-items: start; }
.manual-toc-wrap { position: sticky; top: 16px; }
.manual-toc { background: var(--card, #fff); border: 1px solid var(--border, #e3e3e3); border-radius: 12px; padding: 12px 10px; max-height: calc(100vh - 40px); overflow: auto; }
.manual-toc-title { margin: 0 0 6px; padding: 0 6px; font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.manual-toc ul { list-style: none; margin: 0; padding: 0; }
.manual-toc li a { display: block; padding: 4px 8px; border-radius: 7px; color: var(--ink); text-decoration: none; font-size: 12px; line-height: 1.3; }
.manual-toc li a:hover { background: var(--primary-soft); color: var(--primary-dark); }
.manual-toc .toc-l3 a { padding-left: 20px; font-size: 11px; color: var(--muted); }
/* Decalage d'ancrage pour ne pas masquer le titre sous l'entete. */
.markdown-body :is(h1, h2, h3, h4)[id] { scroll-margin-top: 16px; }
/* Captures d'ecran du manuel. */
.markdown-body .manual-img { display: block; max-width: 100%; height: auto; margin: 12px 0; border: 1px solid var(--line); border-radius: 10px; box-shadow: 0 1px 8px rgba(0, 0, 0, 0.10); }
@media (max-width: 860px) {
    .manual-layout { grid-template-columns: 1fr; }
    .manual-toc-wrap { position: static; }
    .manual-toc { max-height: 260px; }
}

/* ===== Disponibilite des pieces d'un assemblage ===== */
.parts-avail-list { list-style: none; margin: 6px 0 4px; padding: 0; display: grid; gap: 3px; }
.parts-avail { border-left: 3px solid var(--line); border-radius: 0 5px 5px 0; padding: 3px 8px; }
.parts-avail:nth-child(even) { background: rgba(0, 0, 0, .025); }
.parts-avail.ok { border-left-color: #2f9e5a; }
.parts-avail.ko { border-left-color: var(--danger); background: #fdf2f1; }
.pa-head { display: flex; align-items: baseline; gap: 4px 8px; flex-wrap: wrap; line-height: 1.45; }
.pa-mark { font-weight: 900; font-size: 11px; }
.parts-avail.ok .pa-mark { color: #2f9e5a; }
.parts-avail.ko .pa-mark { color: var(--danger); }
.pa-name { font-weight: 700; font-size: 12px; flex: 0 1 auto; color: inherit; text-decoration: none; }
/* La reference vient APRES le libelle : plus petite et grise, elle ne concurrence
   pas la lecture du nom (qui porte le tri alphabetique). */
.pa-sku { font-size: 9.5px; color: var(--muted); font-weight: 600; flex: 0 0 auto; }
.pa-move-tag { flex: 0 0 auto; font-size: 9px; font-weight: 800; text-transform: uppercase; letter-spacing: .02em; color: #1763c2; background: #e7f0fe; border: 1px solid #b9d4fb; border-radius: 999px; padding: 0 6px; }
.pa-head .pa-qty { margin-left: auto; }
.p3d-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 8px; }
a.pa-name:hover, a.mp-name:hover, a.bs-mv-name:hover { text-decoration: underline; }
a.mp-name, a.bs-mv-name { color: inherit; text-decoration: none; }
.pa-qty { font-weight: 700; font-size: 11px; font-variant-numeric: tabular-nums; white-space: nowrap; }
.parts-avail.ok .pa-qty { color: #2f9e5a; }
.parts-avail.ko .pa-qty { color: var(--danger); }
/* Strategie d'assemblage */
.build-strategy { border: 1px solid var(--line); border-left: 4px solid var(--line); border-radius: 10px; padding: 11px 13px; margin: 6px 0 4px; }
.build-strategy.ok { border-left-color: #1c7a3f; background: #f3faf5; }
.build-strategy.ko { border-left-color: var(--danger); background: #fdf4f3; }
.bs-head { margin: 0; }
.bs-sub { margin: 8px 0 4px; font-weight: 700; font-size: 12px; }
.bs-moves { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
.bs-moves li { display: flex; flex-wrap: wrap; gap: 4px 9px; align-items: baseline; font-size: 12px; }
.bs-mv-name { font-weight: 700; }
.bs-mv-qty { font-weight: 800; color: #1763c2; }
.bs-mv-from { color: var(--muted); }
/* Pieces manquantes (liste independante) */
.missing-parts-list { list-style: none; margin: 6px 0 4px; padding: 0; display: grid; gap: 6px; }
.missing-part { display: flex; flex-wrap: wrap; gap: 4px 12px; align-items: baseline; border: 1px solid var(--danger); background: #fdf4f3; border-radius: 8px; padding: 7px 11px; }
.missing-part .mp-name { font-weight: 800; }
.missing-part .mp-sku { font-size: 9.5px; color: var(--muted); font-weight: 600; flex: 1 1 auto; }
.missing-part .mp-qty { font-weight: 700; }
.missing-part .mp-short { color: var(--danger); font-weight: 800; }
.pa-legend { display: flex; flex-wrap: wrap; gap: 4px 5px; margin: 2px 0 8px; }
.pa-locs { display: flex; flex-wrap: wrap; gap: 3px 4px; margin: 1px 0 2px; }
.pa-loc { font-size: 10px; line-height: 1.5; background: #fff; border: 1px solid rgba(0, 0, 0, .12); border-radius: 4px; padding: 0 6px; }
.pa-loc strong { margin-left: 4px; font-variant-numeric: tabular-nums; }
.pa-none { color: var(--muted); font-size: 10px; }

/* ===== Parc de boitiers ===== */
.boitier-search { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.boitier-search input[type=search] { flex: 1 1 240px; min-width: 0; }
.boitier-filters { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px; }
.bt-filter { display: inline-flex; align-items: center; gap: 6px; padding: 4px 11px; border-radius: 999px;
    border: 1px solid var(--line); text-decoration: none; color: var(--ink); font-size: 11.5px; font-weight: 700;
    border-left: 4px solid var(--bt-color, var(--line)); }
.bt-filter:hover { background: var(--primary-soft); }
.bt-filter.active { background: var(--ink); color: #fff; border-color: var(--ink); }
.bt-filter span { font-size: 10px; opacity: 0.75; }
.bt-badge { display: inline-block; padding: 2px 9px; border-radius: 999px; color: #fff; font-size: 10.5px; font-weight: 800; white-space: nowrap; }

/* Sous-titre explicatif sous un titre de panneau. */
.panel-sub { margin: -4px 0 10px; font-size: 11.5px; }

/* Pratiques comptees par un boitier (pietons / velos / voitures...). */
.practice {
    display: inline-flex; align-items: center; gap: 4px;
    margin: 0 4px 2px 0; padding: 1px 8px;
    border: 1px solid #c8d2de; border-radius: 999px;
    background: #eef3f9; color: #2c3b4d; font-size: 10.5px; font-weight: 700; white-space: nowrap;
}
/* Pictogrammes du projet (assets/icons/mobility). */
.practice-icon { width: 14px; height: 14px; display: block; flex: none; }
/* Liste du parc : pictogrammes NUS (sans pastille) dans leur colonne, et le
   libelle dans la colonne immediatement a droite. */
.practice-icons { white-space: nowrap; }
.practice-icons .practice-icon { display: inline-block; vertical-align: middle; margin-right: 5px; }
.practice-icons .practice-icon:last-child { margin-right: 0; }
.practice-text { color: #47566a; }

/* Liste du parc : deux premieres colonnes resserrees. On borne l'element INTERNE
   (et non la cellule) : en mise en page auto, un max-width sur un <td> n'est pas
   respecte, la colonne s'elargirait quand meme au plus long contenu. Les rares
   valeurs plus longues sont coupees par des points de suspension, et restent
   lisibles au survol grace au title. */
.boitier-table .col-sensor a,
.boitier-table .col-serial span {
    display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.boitier-table .col-sensor a,
.boitier-table .col-serial span,
.boitier-table .col-client a,
.boitier-table .col-client span {
    display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.boitier-table .col-sensor a { max-width: 70px; }
.boitier-table .col-serial span { max-width: 82px; }
/* Client : ~30 caracteres. 88 % des noms tiennent entierement ; les plus longs
   (services, interlocuteurs nommes) restent lisibles au survol. */
.boitier-table .col-client a,
.boitier-table .col-client span { max-width: 200px; }
.practice-hint { display: block; margin-top: 2px; color: var(--muted); font-size: 11px; }

/* Fiche boitier : le NUMERO en ovale, pose A CHEVAL sur la bordure superieure du
   bloc Identite (comme une legende de fieldset). Seul ce numero est agrandi ; les
   champs du bloc gardent leur taille normale. */
.bt-identity {
    position: relative;
    border: 2px solid var(--line);
    border-radius: 12px;
    padding: 26px 14px 14px;
    margin-top: 16px;
}
/* Le conteneur est pose a cheval sur la bordure ; il peut porter le numero SEUL
   (formulaire) ou le numero SUIVI DU STATUT (fiche de detail). */
.bt-identity-head {
    position: absolute; top: 0; left: 20px; transform: translateY(-50%);
    display: flex; align-items: center; gap: 10px;
}
.bt-identity-badge {
    padding: 5px 18px; border-radius: 999px;
    background: var(--surface, #fff);
    border: 2px solid var(--primary); color: var(--primary);
    font-size: 15px; font-weight: 800; line-height: 1.2; white-space: nowrap;
}
/* Bouton « Comptage » (fiche boitier) : sur la meme ligne que le numero et le
   statut, avec un petit histogramme dessine. Masque tant que Firestore n'a pas
   confirme l'existence de comptages. */
.counts-btn {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 5px 13px; border-radius: 999px;
    border: 2px solid #0d7d8c; background: var(--surface, #fff); color: #0b6a76;
    font-size: 13px; font-weight: 700; text-decoration: none; white-space: nowrap;
}
.counts-btn:hover { background: #e7f6f8; border-color: #0b6a76; }
.counts-btn[hidden] { display: none; }
.counts-icon { width: 15px; height: 15px; fill: currentColor; flex: none; }

/* Le statut est mis a la MEME taille que le numero (il etait a 10,5px). */
.bt-identity-head .bt-badge { font-size: 15px; padding: 6px 14px; line-height: 1.2; }
/* Page Comptages : le panneau lui-meme sert de cadre, l'ovale se pose sur sa
   bordure superieure (le .bt-identity-head est deja positionne en absolu). */
.bt-framed { position: relative; padding-top: 26px; }
/* Boutons poses sur la ligne du cadre : fond opaque obligatoire, sinon la bordure
   du panneau leur passerait au travers. */
.bt-identity-head .head-btn {
    background: var(--surface, #fff); border-color: #c3ccd8;
    font-weight: 700; text-decoration: none; white-space: nowrap;
}
.bt-identity-head .head-btn:hover { border-color: var(--primary); color: var(--primary); }

/* Creation : pas encore de numero, l'ovale reste discret (meme taille, ton attenue). */
.bt-identity-badge.is-empty { border-color: var(--line); color: var(--muted); font-weight: 700; }

/* Parc de boitiers : lignes condensees (le parc depasse le millier de fiches, on
   veut en voir un maximum d'un coup d'oeil). Les pastilles et les boutons de la
   ligne sont resserres aussi, sinon ce sont EUX qui imposent la hauteur. */
.boitier-table th, .boitier-table td { padding: 2px 7px; line-height: 1.25; vertical-align: middle; }
.boitier-table .bt-badge, .boitier-table .ship-badge { padding: 1px 8px; }
.boitier-table .button.small { padding: 2px 8px; line-height: 1.3; }

/* Comportement « tableur » : une valeur ne passe JAMAIS sur deux lignes (la table
   s'elargit et defile horizontalement), et l'en-tete reste visible au defilement.
   Le sticky exige un conteneur qui defile lui-meme, d'ou la hauteur max ici. */
.boitier-table th, .boitier-table td { white-space: nowrap; }
.boitier-table-wrap { max-height: calc(100vh - 250px); overflow: auto; }

/* En-tetes de colonne TRIABLES (table[data-sortable]). Le chevron n'apparait que
   sur la colonne active, sinon l'en-tete devient illisible. La place lui est
   reservee en permanence (::after vide) pour que le libelle ne saute pas au clic. */
th.is-sortable { cursor: pointer; user-select: none; white-space: nowrap; }
th.is-sortable:hover { color: var(--primary); }
th.is-sortable::after { content: ""; display: inline-block; width: 9px; margin-left: 3px; opacity: .55; }
th.is-sortable.sorted-asc::after { content: "\25B2"; opacity: 1; }
th.is-sortable.sorted-desc::after { content: "\25BC"; opacity: 1; }
th.is-sortable.sorted-asc, th.is-sortable.sorted-desc { color: var(--primary); }
th.is-sortable:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }

/* Voile d'attente (data-busy) : couvre l'ecran pendant une operation lente, pour
   qu'on voie que le clic a ete pris et qu'on ne reclique pas. */
.busy-overlay {
    position: fixed; inset: 0; z-index: 9999;
    display: flex; align-items: center; justify-content: center;
    background: rgba(20, 30, 45, .45);
    backdrop-filter: blur(2px);
    /* Bloque les clics pendant le traitement : relancer l'operation en cours
       doublerait le travail cote serveur. */
    cursor: progress;
}
.busy-box {
    display: flex; flex-direction: column; align-items: center; gap: 10px;
    min-width: 260px; max-width: 90vw;
    padding: 24px 30px;
    border: 1px solid var(--primary); border-radius: 10px;
    background: var(--surface, #fff);
    box-shadow: 0 12px 34px rgba(20, 30, 45, .30);
    text-align: center;
}
.busy-spin {
    width: 26px; height: 26px;
    border: 3px solid var(--line, #e3e6ea);
    border-top-color: var(--primary);
    border-radius: 50%;
    animation: busy-turn .8s linear infinite;
}
@keyframes busy-turn { to { transform: rotate(360deg); } }
.busy-msg { margin: 0; font-size: 13.5px; font-weight: 800; color: var(--ink); }
/* Jauge : masquee tant que le serveur n'a pas annonce un total connu — une barre
   qui avance sans rien mesurer serait un decor trompeur. */
.busy-bar {
    width: 240px; max-width: 100%; height: 8px;
    background: var(--line, #e3e6ea); border-radius: 999px; overflow: hidden;
}
.busy-bar[hidden] { display: none; }
.busy-bar-fill {
    display: block; width: 0; height: 100%;
    background: var(--primary); border-radius: 999px;
    transition: width .3s ease;
}
.busy-pct { margin: 0; font-size: 12px; font-weight: 700; color: var(--primary); font-variant-numeric: tabular-nums; }
.busy-pct[hidden] { display: none; }
.busy-sub { margin: 0; font-size: 11.5px; color: var(--muted); }
/* Respect du reglage systeme « animations reduites » : le voile reste, l'animation
   s'arrete (elle peut declencher des malaises chez certaines personnes). */
@media (prefers-reduced-motion: reduce) {
    .busy-spin { animation: none; border-top-color: var(--primary); }
}

/* Barre au-dessus de la liste : compteur a gauche, choix du tri a droite. */
.list-toolbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 4px; }
.list-toolbar > p { margin: 0; margin-right: auto; }
/* Compteur mis en avant, a la couleur du filtre actif (--cnt-color, pose en PHP).
   Le NOMBRE ressort encore un cran au-dessus du libelle. */
.list-count {
    margin: 0;
    font-size: 13px;
    font-weight: 800;
    color: var(--cnt-color, var(--primary));
}
.list-count [data-live-count] {
    font-size: 17px;
    /* tabular-nums : le compteur change a chaque frappe dans le filtre ; sans chiffres
       de largeur fixe, le texte qui suit tressaute. */
    font-variant-numeric: tabular-nums;
}
.sort-pick {
    display: inline-flex; align-items: center; gap: 6px;
    margin: 0; font-size: 11px; color: var(--muted); font-weight: 700; white-space: nowrap;
}
/* Le select herite sinon de la largeur pleine des champs de formulaire. */
.sort-pick select { width: auto; min-width: 140px; min-height: 26px; padding: 3px 8px; font-size: 12px; }
[data-sort-dir] { min-width: 30px; padding: 3px 8px; line-height: 1; }
.boitier-table thead th {
    position: sticky; top: 0; z-index: 2;
    background: var(--surface, #fff);
    box-shadow: inset 0 -1px 0 var(--line);
}

/* ===== Fenetre « nouveautes de la version » ===== */
/* Affichee a chaque ouverture tant que l'utilisateur n'a pas clique « Ne plus
   afficher ». Volontairement modale : c'est une annonce, elle doit etre vue. */
.release-modal {
    position: fixed;
    inset: 0;
    z-index: 1200;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background: rgba(23, 32, 42, .55);
}
.release-modal[hidden] { display: none !important; }
.release-box {
    display: flex;
    flex-direction: column;
    width: min(720px, 100%);
    max-height: min(80vh, 720px);
    background: var(--surface);
    border: 1px solid var(--primary);
    border-radius: 12px;
    box-shadow: 0 24px 60px rgba(0, 0, 0, .28);
    overflow: hidden;
}
.release-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    padding: 14px 16px;
    border-bottom: 1px solid var(--line);
}
.release-head h2 { margin: 2px 0 0; font-size: 17px; }
.release-close { flex: 0 0 auto; width: 26px; min-height: 26px; padding: 0; font-size: 15px; font-weight: 800; line-height: 1; }
/* Le corps defile : un changelog peut etre long, l'entete et les boutons restent. */
.release-body { overflow-y: auto; padding: 4px 16px 12px; }
.release-body h2 { font-size: 14px; margin: 12px 0 6px; }
.release-body h3 { font-size: 12.5px; margin: 10px 0 4px; }
.release-body ul { margin: 4px 0; padding-left: 18px; }
.release-body li { margin: 2px 0; }
.release-foot {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    padding: 12px 16px;
    border-top: 1px solid var(--line);
    background: #fbfcfd;
}

/* ===== Fiche assemblage : vue 3D + simulateur en haut ===== */
/* La 3D est volontairement COMPACTE (360px) : elle illustre, elle n'est plus le
   sujet principal. Le simulateur prend le reste. Les listes passent dessous. */
.asm-top {
    display: grid;
    grid-template-columns: minmax(0, 360px) minmax(0, 1fr);
    gap: 18px;
    align-items: start;
    margin-bottom: 18px;
}
/* Pas de 3D / pas de photo : le simulateur prend toute la largeur. */
.asm-top > .asm-sim-pane:only-child { grid-column: 1 / -1; }
/* Le bas de la fiche (infos + listes de pieces) occupe TOUTE la largeur : la vue 3D
   est remontee en haut, plus rien ne justifie de reserver une colonne a droite. */
.asm-bottom { display: grid; gap: 18px; }
/* Les deux volets sont des colonnes flex : leurs TITRES sont donc sur la meme ligne
   (align-items:start sur la grille), et chaque bloc commence SOUS son titre.
   Le margin-bottom NEGATIF par defaut de .panel-title-out (-8px) ferait remonter le
   bloc par-dessus le titre : on le neutralise ici, comme .pv-pane le fait deja. */
.asm-visual, .asm-sim-pane { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.asm-visual > .panel-title-out, .asm-sim-pane > .panel-title-out { margin: 0; }
.asm-visual > .model-inline { height: 300px; }
.asm-visual > .part-photo { width: 100%; }
.build-sim { display: flex; flex-direction: column; gap: 10px; }
.bsim-intro { margin: 0; font-size: 10.5px; }
.bsim-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.bsim-row label { font-weight: 700; white-space: nowrap; }
.bsim-row input { width: 90px; min-width: 0; }
/* Croix de remise a 1 : carree, collee au champ. */
.bsim-reset {
    flex: 0 0 auto;
    width: 26px;
    min-height: 26px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 14px;
    font-weight: 800;
    line-height: 1;
}
.bsim-hint { font-size: 10.5px; color: var(--muted); }
.bsim-result:empty { display: none; }
.bsim-ok, .bsim-ko { border-radius: 8px; padding: 9px 11px; font-weight: 700; }
.bsim-ok { background: #e4f6ea; color: #1c7a3f; border: 1px solid #a9dcbd; }
.bsim-ko { background: #fdf4f3; color: var(--danger); border: 1px solid var(--danger); }
.bsim-list { list-style: none; margin: 8px 0 0; padding: 0; display: grid; gap: 5px; }
.bsim-item {
    display: flex; align-items: baseline; gap: 4px 10px; flex-wrap: wrap;
    border: 1px solid var(--danger); background: #fdf4f3;
    border-radius: 8px; padding: 6px 10px;
}
/* Ce qu'il faut FAIRE de la piece manquante : imprimer / commander / fabriquer.
   Chaque action a sa couleur pour se reperer d'un coup d'oeil dans la liste. */
.bsim-act {
    flex: 0 0 auto;
    display: inline-block;
    padding: 1px 7px;
    border-radius: 999px;
    font-size: 9px;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: .02em;
    white-space: nowrap;
}
.bsim-print { background: #e0f2fe; color: #075985; }   /* imprimable : on la produit */
.bsim-order { background: #ffedd5; color: #9a3412; }   /* a acheter : delai fournisseur */
.bsim-build { background: #ede9fe; color: #5b21b6; }   /* sous-assemblage : a fabriquer */
.bsim-name { font-weight: 800; color: inherit; text-decoration: none; }
a.bsim-name:hover { text-decoration: underline; }
.bsim-sku { font-size: 9.5px; color: var(--muted); font-weight: 600; flex: 1 1 auto; }
.bsim-qty { font-weight: 700; white-space: nowrap; }
.bsim-short { color: var(--danger); font-weight: 800; white-space: nowrap; }
@media (max-width: 860px) { .asm-top { grid-template-columns: 1fr; } }

/* ===== Lien fournisseur (fiche piece) ===== */
/* Le champ URL et son bouton « Ouvrir » sur une seule ligne. */
.supplier-url-field { display: flex; align-items: center; gap: 6px; }
.supplier-url-field input { flex: 1 1 auto; min-width: 0; }
.supplier-url-open { flex: 0 0 auto; white-space: nowrap; }
/* [hidden] est ecrase par .button{display:inline-flex} : il faut !important. */
.supplier-url-open[hidden] { display: none !important; }
/* Le code fournisseur se lit/recopie souvent : chasse fixe, selectionnable. */
.supplier-ref { font-family: Consolas, "Courier New", monospace; user-select: all; }

/* ===== Location ===== */
/* Les trois couleurs du module : VERT en cours, ORANGE retour proche (7 j),
   ROUGE en retard. Un retard doit sauter aux yeux — c'est l'information la
   plus actionnable — et l'orange doit prevenir avant qu'il ne soit trop tard. */
/* Un retard s'affiche BLANC SUR ROUGE. La couleur du texte est indispensable :
   sans elle, le badge herite de l'encre sombre et devient illisible sur le
   fond rouge. */
.ship-badge.ship-late { background: var(--danger); color: #fff; }
.ship-badge.ship-soon { background: #f08c00; color: #fff; }
.ship-badge.ship-ok { background: var(--success); color: #fff; }
/* Le nombre de jours de retard porte le meme code : blanc sur rouge. */
.rental-late {
    display: inline-block; padding: 1px 6px; border-radius: 999px;
    background: var(--danger); color: #fff;
    font-weight: 800; font-size: 10px; white-space: nowrap;
}
.rental-soon { color: #b36b00; font-weight: 800; font-size: 10px; white-space: nowrap; }
/* Liste des locations : la ligne entiere ouvre la location (simple clic). Le
   survol se fait en SURIMPRESSION, pour ne pas ecraser le fond colore des lignes
   en retard / retour proche. */
.rentals-table tbody tr { cursor: pointer; }
.rentals-table tbody tr:hover { box-shadow: inset 0 0 0 9999px rgba(29, 111, 208, .07); }

.rental-row-retard { background: #fff1f1; }
.rental-row-retard td { border-color: #f3b6b6; }
.rental-row-bientot { background: #fff8ec; }
.rental-row-bientot td { border-color: #f0d3a4; }
.rental-tabs { display: flex; gap: 6px; margin: 0 0 12px; flex-wrap: wrap; }
/* Le formulaire de retour : cases a cocher + date + note. */
.rental-return-form input[type="date"] { width: 150px; min-width: 0; }

/* Sauvegardes : une mensuelle (archive permanente) se repere d'un coup d'oeil. */
.backup-permanent { background: #eaf6ee; }
.backup-permanent td { border-color: #b7dcc4; }

/* Frise d'un boitier (fiche parc) : evenements dates, pastille coloree par type. */
.timeline { list-style: none; margin: 0; padding: 0; }
.timeline-item { position: relative; padding: 0 0 14px 22px; border-left: 2px solid #e3e6ea; }
.timeline-item:last-child { border-left-color: transparent; }
.timeline-dot { position: absolute; left: -7px; top: 3px; width: 12px; height: 12px; border-radius: 50%; box-shadow: 0 0 0 3px #fff; }
.timeline-head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.timeline-date { font-weight: 600; min-width: 84px; }
.timeline-client { color: #333; }
.timeline-note { margin: 3px 0 0; color: #555; font-size: 0.92em; }
.timeline-link { font-size: 0.85em; }

/* Formulaire d'intervention : cases en ligne, deux colonnes de check-lists. */
.check-inline { display: flex; align-items: center; gap: 6px; }
.check-inline input { width: auto; }
.form-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin: 12px 0; }
.form-cols fieldset { border: 1px solid #e3e6ea; border-radius: 8px; padding: 10px 14px; }
.form-cols legend { font-weight: 600; padding: 0 6px; }
.form-cols .check-inline { margin: 4px 0; }
@media (max-width: 720px) { .form-cols { grid-template-columns: 1fr; } }

/* Formulaire d'intervention (« Preparation de compteur ») : ~30 champs. Meme
   compactage que les fiches client et boitier, pour tenir sur un 1920x1080 sans
   defilement. */
.intervention-form { gap: 6px; }
.intervention-form .form-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 8px 16px;
    align-items: start;
}
.intervention-form .form-grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.intervention-form .form-grid > label,
.intervention-form input, .intervention-form select, .intervention-form textarea { min-width: 0; }
.intervention-form h2 {
    margin: 8px 0 0; padding-top: 6px; border-top: 1px solid var(--line);
    font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted);
}
.intervention-form label { font-size: 12px; }
.intervention-form input, .intervention-form select, .intervention-form textarea {
    padding: 4px 8px; font-size: 12.5px;
}
.intervention-form textarea { resize: vertical; }
/* REGULARITE DES LIGNES : une case a cocher ne fait que ~20px de haut, un champ
   ~48px (libelle + saisie). Sans align-self, la case flotte en haut de sa rangee
   et se decale des champs voisins. Calee en bas, elle s'aligne sur leur saisie. */
.intervention-form .form-grid > .check-inline { align-self: end; min-height: 31px; }
/* Les deux check-lists : plus ramassees, et a hauteur egale. */
.intervention-form .form-cols { gap: 12px; margin: 8px 0; align-items: start; }
.intervention-form .form-cols fieldset { padding: 6px 12px 8px; }
.intervention-form .form-cols legend {
    padding: 0 5px; font-size: 11px; letter-spacing: .05em;
    text-transform: uppercase; color: var(--muted); font-weight: 700;
}
.intervention-form .form-cols .check-inline { margin: 1px 0; font-size: 12px; }
/* Ligne de cases isolees (Magneto / BLE). */
.intervention-form .check-row { display: flex; flex-wrap: wrap; gap: 8px 26px; margin: 6px 0; }
.intervention-form .check-row .check-inline { font-size: 12px; }
.intervention-form .form-actions { margin-top: 8px; }
@media (max-width: 1200px) {
    .intervention-form .form-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Fiche vente : pastilles des numeros de boitiers vendus. */
.sale-units { display: flex; flex-wrap: wrap; gap: 6px; }
.sale-unit-badge { background: #6b4fbb; color: #fff; text-decoration: none; }
a.sale-unit-badge:hover { filter: brightness(1.1); }
.sale-unit-unknown { background: #888; }

/* Parc : boitiers actuellement en location (liaison avec le module Location). */
.boitier-rented { background: #eef4fd; }
.loc-banner { margin: 0 0 16px; padding: 12px 16px; border-radius: 8px; background: #eef4fd; border: 1px solid #b9d3f5; }
.loc-banner a { margin-left: 6px; }

/* Location vs Prêt : pastille de type dans le module. */
.rental-kind { display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: 0.82em; font-weight: 600; color: #fff; }
.rental-kind-location { background: #1d6fd0; }
.rental-kind-pret { background: #0d9488; }

/* Referentiel clients : usage, orphelins. */
.usage-badge { display: inline-block; min-width: 22px; text-align: center; padding: 2px 8px; border-radius: 999px; background: #e7edf5; color: #244; font-weight: 600; font-size: 0.85em; }
/* Page Locations : indicateurs a droite du bouton « Nouvelle location ».
   Cliquables — ils mènent a la vue « Boitiers dehors » deja filtree. */
.rental-kpis { align-items: stretch; gap: 10px; flex-wrap: wrap; }
/* Cartes ENCADREES : chiffre en grand, libelle dessous, cadre net et ombre legere
   pour qu'elles se detachent franchement du fond. */
.rental-kpi {
    display: inline-flex; flex-direction: column; align-items: center; justify-content: center;
    min-width: 112px; padding: 7px 15px;
    border: 2px solid; border-radius: 10px;
    text-decoration: none; line-height: 1.15; white-space: nowrap;
    box-shadow: 0 1px 3px rgba(20, 30, 45, .12);
}
.rental-kpi strong { font-size: 23px; font-weight: 800; letter-spacing: -0.02em; }
.rental-kpi span { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; opacity: .9; }
.kpi-late  { color: #8e1b12; border-color: #d98a80; background: #fdecea; }
.kpi-soon  { color: #8a5200; border-color: #dcb670; background: #fdf4e3; }
.kpi-ok    { color: #14663a; border-color: #86c9a5; background: #eaf7ef; }
.kpi-total { color: #37424f; border-color: #b9c2ce; background: #f3f5f8; }
a.rental-kpi:hover { filter: brightness(0.96); box-shadow: 0 2px 7px rgba(20, 30, 45, .18); }
a.rental-kpi:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

/* Fiche client compacte : pensee pour tenir sur un ecran 1920x1080 (fiche +
   historique + fusion). 4 colonnes au lieu de 2, titres de section en filets
   discrets, champs et interlignes resserres. */
.client-form { gap: 8px; }
.client-form .form-grid { grid-template-columns: repeat(4, minmax(150px, 1fr)); gap: 6px 14px; }
.client-form h2.panel-title {
    margin: 6px 0 0; padding-top: 6px; border-top: 1px solid var(--line);
    font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted);
}
.client-form label { font-size: 12px; }
.client-form input, .client-form select, .client-form textarea { padding: 4px 8px; font-size: 12.5px; }
.client-form textarea { resize: vertical; }
.client-form > p.muted { margin: 0; font-size: 11.5px; }
.client-form .form-actions { margin-top: 2px; }
/* Sous 1200px de large, on repasse a 2 colonnes pour rester lisible. */
@media (max-width: 1200px) { .client-form .form-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* Formulaire de la fiche BOITIER : meme traitement que la fiche client — 4 colonnes,
   titres de section en filets discrets, champs resserres. Le tout tient ainsi sans
   defilement sur un 1920x1080. */
.boitier-form { gap: 8px; }
.boitier-form .form-grid {
    /* minmax(0, 1fr) et non minmax(150px, 1fr) : un minimum rigide empeche les
       colonnes de se reduire et fait DEBORDER la grille a droite (les champs date
       ont en plus une largeur intrinseque incompressible). A 0, la grille tient
       toujours dans la largeur de la page, a parts egales. */
    grid-template-columns: repeat(4, minmax(0, 1fr));
    /* Interligne franc (12px) : les 6 dates tiennent sur 2 rangees et se touchaient. */
    gap: 12px 18px;
    /* SANS ceci, une cellule plus haute (Technologie porte l'indice « Compte : … »)
       etire ses voisines : label est un grid, la hauteur en trop part dans l'input,
       et Version / Firmware / SIM se retrouvent plus hauts que les autres. */
    align-items: start;
}
/* Meme raison a l'interieur du label : le champ garde sa hauteur naturelle. */
.boitier-form .form-grid label { align-content: start; }
/* Sans min-width:0, un label (et son input date) refuse de descendre sous sa
   largeur intrinseque et ressort de sa colonne. */
.boitier-form .form-grid > label,
.boitier-form .form-grid input,
.boitier-form .form-grid select { min-width: 0; }
/* Le champ date est plus etroit que les autres : il n'a jamais que jj/mm/aaaa a
   afficher, inutile qu'il occupe toute la colonne. La largeur est portee par le
   conteneur .date-wrap (pose par app.js), pour que la croix se cale dessus. */
/* Largeur PARTOUT : un champ date n'a que jj/mm/aaaa a montrer. Sans plafond il
   prend toute sa colonne et, sa largeur intrinseque etant incompressible, il
   debordait sur le champ voisin (« Type » dans le bloc Premiere preparation). */
.date-wrap { position: relative; display: block; min-width: 0; max-width: 175px; }
/* Place pour la croix ET pour l'icone calendrier native, a droite. */
.date-wrap input[type="date"] { width: 100%; max-width: none; padding-right: 46px; }
.date-clear {
    position: absolute; right: 25px; top: 50%; transform: translateY(-50%);
    width: 17px; height: 17px; padding: 0; min-height: 0;
    display: flex; align-items: center; justify-content: center;
    border: 0; border-radius: 50%;
    background: #e8ecf1; color: #5b6675;
    font-size: 13px; font-weight: 700; line-height: 1; cursor: pointer;
}
.date-clear:hover { background: var(--danger, #b3261e); color: #fff; }
.date-clear[hidden] { display: none; }
/* Section Dates : 6 champs sur 2 rangees, ils se lisaient comme un bloc compact.
   Ecart plus large + filet vertical entre les colonnes pour separer franchement. */
.boitier-form .form-grid-dates { gap: 20px 28px; }
.boitier-form .form-grid-dates > label { padding-right: 14px; border-right: 1px solid var(--line); }
.boitier-form .form-grid-dates > label:nth-child(4n) { padding-right: 0; border-right: 0; }
@media (max-width: 1200px) {
    .boitier-form .form-grid-dates > label:nth-child(4n) { padding-right: 14px; border-right: 1px solid var(--line); }
    .boitier-form .form-grid-dates > label:nth-child(2n) { padding-right: 0; border-right: 0; }
}
.boitier-form h2 {
    margin: 8px 0 0; padding-top: 6px; border-top: 1px solid var(--line);
    font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted);
}
.boitier-form label { font-size: 12px; }
.boitier-form input, .boitier-form select, .boitier-form textarea { padding: 4px 8px; font-size: 12.5px; }
.boitier-form textarea { resize: vertical; }
.boitier-form .practice-hint { font-size: 10.5px; }
.boitier-form .form-actions { margin-top: 2px; }
/* Le formulaire EST le cadre : son ovale se pose sur sa bordure orange. Le premier
   titre de section ne doit donc pas ajouter son filet juste sous l'ovale. */
.boitier-form.bt-framed { padding-top: 22px; }
/* Barre d'action pleine largeur (le .page-header est en colonne, align-items:flex-start :
   sans align-self, la barre se retrecit a ses boutons et rien ne peut etre cadre a droite). */
.page-header > .form-actions-wide { width: 100%; align-self: stretch; }
/* Action destructrice cadree sur le bord droit, loin du bouton d'enregistrement. */
.page-header .form-actions-wide .btn-detached { margin-left: auto; }

/* Formulaire « Nouvelle commande » : la regle .compact imposait des colonnes
   INEGALES (2fr 1fr 1fr 2fr), d'ou des champs de largeurs disparates. On repasse a
   4 colonnes egales, et align-items:start empeche la note sous « Date d'arrivee
   prevue » d'etirer la hauteur des champs voisins. */
.order-form.compact .form-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 12px 18px;
    align-items: start;
}
.order-form .form-grid > label { align-content: start; min-width: 0; }
.order-form input, .order-form select { min-width: 0; }
/* La note d'aide sort du flux vertical : elle ne doit pas decaler le champ. */
.order-form .form-grid small.muted {
    display: block; margin-top: 3px; font-size: 10.5px; line-height: 1.25;
}
/* Le champ Piece est le plus long a saisir : il occupe deux colonnes. */
.order-form.compact .form-grid > label:first-child { grid-column: span 2; }
.order-form .date-wrap { max-width: none; }
@media (max-width: 1200px) {
    .order-form.compact .form-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .order-form.compact .form-grid > label:first-child { grid-column: span 2; }
}

/* Bouton RETOUR : icone seule, en tete de barre. Etroit pour ne pas voler la place
   du bouton principal, et legerement detache de lui. */
.page-header .nav-back {
    padding-left: 7px; padding-right: 7px;
    margin-right: 3px;
    color: var(--muted);
}
.page-header .nav-back:hover { color: var(--primary); border-color: var(--primary); }
.page-header .nav-back .btn-ico { width: 14px; height: 14px; }

/* Tableau de bord, premier bloc : locations/prets a GAUCHE, maintenances a DROITE.
   align-items:start pour que le panneau le plus court ne s'etire pas a la hauteur
   de l'autre. Quand un seul des deux existe, il prend toute la largeur. */
/* align-items:stretch (defaut) : les deux panneaux prennent la MEME hauteur, celle
   de la rangee. C'est ce qui les aligne visuellement meme si l'un a 2 lignes et
   l'autre 40. */
.dash-watch {
    display: grid; grid-template-columns: 1fr 1fr; gap: 12px; align-items: stretch;
    /* Respiration avant « Activite recente » : les panneaux internes ont une marge
       basse a 0, sans ceci le bloc collerait au panneau suivant. */
    margin-bottom: 16px;
}
/* Le panneau devient une colonne flex : titre + sous-titre figes en haut, la liste
   occupe le reste. Sans cela, le tableau ne saurait pas quelle hauteur remplir. */
.dash-watch > .panel {
    margin-bottom: 0; min-width: 0;
    display: flex; flex-direction: column;
    max-height: 340px;   /* borne haute : le tableau de bord reste lisible d'un coup d'oeil */
}
.dash-watch > .panel:only-child { grid-column: 1 / -1; }
.dash-watch > .panel > .panel-title { flex: 0 0 auto; }
/* min-height:0 est INDISPENSABLE : sans lui un enfant flex refuse de descendre
   sous sa hauteur de contenu, l'ascenseur n'apparait jamais et le panneau deborde. */
.dash-watch .table-wrap { flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: auto; }
/* L'en-tete reste visible pendant le defilement de la liste. */
.dash-watch .table-wrap thead th {
    position: sticky; top: 0; z-index: 1;
    background: var(--surface, #fff);
}
.dash-watch th, .dash-watch td { padding: 3px 7px; }
.dash-watch .panel-sub { margin: 0 0 6px; font-size: 11.5px; }
/* Preparation de compteur laissee EN BROUILLON : elle attend qu'on la reprenne.
   Teinte ambre, distincte des statuts metier (verts/rouges) pour ne pas se lire
   comme un etat du boitier. */
.draft-badge { background: #ffe9c2; color: #8a5a00; border: 1px solid #e8b869; }
tr.is-draft { background: #fffaf0; }
tr.is-draft td { border-left: 0; }
tr.is-draft td:first-child { box-shadow: inset 3px 0 0 #e8b869; }

/* Interrupteur a glissiere (parc de boitiers : afficher les hors service).
   La case native est masquee mais reste focalisable au clavier — d'ou l'anneau de
   focus porte par la glissiere. */
.switch { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; user-select: none; }
.switch input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; }
.switch-track {
    position: relative; flex: none;
    width: 34px; height: 19px; border-radius: 999px;
    background: #c8ced7; transition: background .15s ease;
}
.switch-knob {
    position: absolute; top: 2px; left: 2px;
    width: 15px; height: 15px; border-radius: 50%;
    background: #fff; box-shadow: 0 1px 2px rgba(20, 30, 45, .35);
    transition: transform .15s ease;
}
.switch input:checked + .switch-track { background: var(--primary); }
.switch input:checked + .switch-track .switch-knob { transform: translateX(15px); }
.switch input:focus-visible + .switch-track { outline: 2px solid var(--primary-dark, var(--primary)); outline-offset: 2px; }
.switch input:disabled + .switch-track { opacity: .55; }
.switch-label { font-size: 11px; font-weight: 700; color: var(--muted); white-space: nowrap; }
.switch input:checked ~ .switch-label { color: var(--ink); }
.switch-count {
    display: inline-block; margin-left: 3px; padding: 0 5px;
    border-radius: 999px; background: rgba(0, 0, 0, .07); color: var(--muted);
}

/* Titre de panneau cliquable : il mene au module correspondant. Rendu comme un
   bouton discret pour qu'on voie qu'il est actionnable, sans alourdir l'en-tete. */
.panel-title-link {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 3px 9px; margin: -3px -9px;
    border: 1px solid transparent; border-radius: 7px;
    color: inherit; text-decoration: none;
}
.panel-title-link::after { content: "\203A"; font-weight: 400; color: var(--muted); }
.panel-title-link:hover {
    background: var(--primary-soft, #fdf0e6);
    border-color: var(--primary);
    color: var(--primary-dark, var(--primary));
}
.panel-title-link:hover::after { color: inherit; }
@media (max-width: 1200px) { .dash-watch { grid-template-columns: 1fr; } }

/* « Nouveau boitier » : bloc « Premiere preparation ». Il reprend exactement la mise
   en page du formulaire d'intervention (4 colonnes, champs resserres). */
.boitier-form .prep-toggle { margin: 4px 0 2px; font-size: 12px; font-weight: 700; color: var(--ink); }
.boitier-form .prep-block[hidden] { display: none; }
/* Repli AUSSI en CSS pur : app.js commence par un import de module, et si celui-ci
   echoue le script entier ne s'execute pas — le bloc resterait alors affiche malgre
   le switch sur OFF. Cette regle ne depend d'aucun JS. */
.boitier-form:has([data-prep-toggle]:not(:checked)) [data-prep-block] { display: none; }
.boitier-form .form-grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.boitier-form .form-grid > .check-inline { align-self: end; min-height: 31px; }
.boitier-form .form-cols { gap: 12px; margin: 8px 0; align-items: start; }
.boitier-form .form-cols fieldset { padding: 6px 12px 8px; }
.boitier-form .form-cols legend {
    padding: 0 5px; font-size: 11px; letter-spacing: .05em;
    text-transform: uppercase; color: var(--muted); font-weight: 700;
}
.boitier-form .form-cols .check-inline { margin: 1px 0; font-size: 12px; }
.boitier-form .check-row { display: flex; flex-wrap: wrap; gap: 8px 26px; margin: 6px 0; }
.boitier-form .check-row .check-inline { font-size: 12px; }
.boitier-form h2:first-of-type { margin-top: 2px; }
@media (max-width: 1200px) { .boitier-form .form-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* Panneaux Historique / Fusion de la fiche client : plus ramasses. */
.client-panel { padding: 10px 13px; }
.client-panel .panel-title { margin: 0 0 6px; font-size: 13px; }
.client-panel h3 { margin: 8px 0 2px; font-size: 12px; }
.client-panel th, .client-panel td { padding: 2px 7px; }
.client-panel .table-wrap { max-height: 190px; overflow: auto; }
.client-panel > p.muted { margin: 2px 0 0; font-size: 11.5px; }

/* Liste clients : lignes alternees BLANC / ORANGE TRES PALE, ouverture au
   double-clic sur la ligne entiere. */
.client-table tbody tr { cursor: pointer; }
.client-table tbody tr:nth-child(odd) { background: #fff; }
.client-table tbody tr:nth-child(even) { background: #fdf3ea; }
.client-table tbody tr:hover { background: #fbe6d2; }
/* Le nom ne doit pas se lire comme un lien : ni couleur de lien, ni soulignement.
   Le lien reste dans le DOM (accessibilite clavier) mais n'occupe aucune place. */
.client-table td a.row-key-link {
    position: absolute; width: 1px; height: 1px;
    margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%);
}
.client-table td a.row-key-link:focus-visible {
    position: static; width: auto; height: auto; margin: 0; clip-path: none;
    outline: 2px solid var(--primary); outline-offset: 2px;
}
/* Client jamais reference : signale par un lisere lateral et un nom attenue —
   PAS par un fond, qui ecraserait le zebrage. */
.client-orphan { box-shadow: inset 3px 0 0 #c3cad3; }
.client-orphan td:first-child strong { color: var(--muted); font-weight: 600; }
/* Colonne « Boitiers » : etroite et centree, pour que les ovales s'alignent. */
.client-table .col-btcount { width: 1%; white-space: nowrap; text-align: center; }

/* Matrice des droits par type de compte. */
.perm-table th, .perm-table td { padding: 4px 10px; vertical-align: middle; }
.perm-table .perm-col { width: 130px; text-align: center; }
.perm-table .perm-group th {
    background: var(--primary-soft, #fdf0e6); color: var(--primary-dark, var(--primary));
    font-size: 11px; text-transform: uppercase; letter-spacing: .05em; padding: 5px 10px;
}
.perm-key { font-size: 9.5px; color: var(--muted); }
/* L'interrupteur est ici dans une cellule : il se centre, sans libelle. */
.perm-switch { display: inline-flex; margin: 0; }
.perm-switch .switch-label { display: none; }
/* Colonne « Super » : non reglable, volontairement attenuee. */
.perm-super { background: #f6f8fa; }
.perm-always { color: #1c7a3f; font-weight: 800; font-size: 15px; }

/* Filtres des documents Axonaut : periode + client, sur une ligne. */
.axo-filters { display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; }
.axo-filters label { font-size: 10px; }
.axo-filters .combo { min-width: 230px; }
.axo-filters input[type="date"] { max-width: 150px; }

/* ---------- Rapport « reunion delivery » ---------- */
/* Date de la prochaine reunion, sous le libelle du menu. En bloc : elle passe a la
   ligne sous « Reunion delivery » au lieu de l'allonger. */
.nav-sub-date {
    display: block; margin-top: 1px;
    font-size: 9.5px; font-weight: 600; letter-spacing: .02em;
    color: var(--muted); line-height: 1.2;
}
.nav-item.active .nav-sub-date { color: inherit; opacity: .8; }
/* Pastilles des numeros de boitiers, dans le suivi groupe par client. */
.dv-nums { line-height: 1.9; }
.dv-chip {
    display: inline-block; margin: 0 3px 2px 0; padding: 1px 7px;
    border: 1px solid var(--line); border-radius: 999px;
    background: var(--surface, #fff); color: inherit;
    font-size: 10.5px; font-weight: 700; text-decoration: none; white-space: nowrap;
}
.dv-chip:hover { border-color: var(--primary); color: var(--primary); }
.delivery-periode { margin: -4px 0 10px; }
/* Deux colonnes semaine passee / semaine en cours, comme le rapport papier. */
.delivery-table { table-layout: fixed; }
.delivery-table th.dv-row {
    width: 130px; vertical-align: top; text-align: left;
    font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--ink);
}
.delivery-table td { vertical-align: top; }
.dv-list { margin: 0 0 4px; padding-left: 17px; }
.dv-list li { margin: 1px 0; line-height: 1.35; }
.dv-sub { margin: 4px 0 2px; font-size: 11px; font-weight: 700; color: var(--muted); }
.dv-num { text-align: right; white-space: nowrap; }
.dv-stock td, .dv-stock th { padding: 3px 7px; }
/* Sous le minimum : la ligne doit sauter aux yeux pendant la reunion. */
.dv-alert { background: #fdecea; }
.dv-alert td:first-child { box-shadow: inset 3px 0 0 var(--danger, #b3261e); }
/* Listes longues bornees a l'ecran ; l'impression les deroule entierement. */
.dv-scroll { max-height: 340px; overflow: auto; }
.dv-printonly { display: none; }

/* IMPRESSION : on ne garde que le rapport. Sans ceci, le menu et les boutons
   partiraient sur le papier et le tableau serait coupe a la premiere page. */
@media print {
    /* PAYSAGE : le tableau de synthese a deux colonnes larges et l'etat du stock
       trois colonnes — en portrait tout se tasse et se coupe. */
    @page { size: A4 landscape; margin: 10mm; }
    nav, aside, .sidebar, .page-header .form-actions, .presence-bar,
    .account-quick-row, .topbar, .dv-noprint { display: none !important; }
    .main { margin: 0 !important; padding: 0 !important; }
    .panel {
        border: 1px solid #999; box-shadow: none; break-inside: avoid;
        margin-bottom: 8px;
    }
    .dv-scroll { max-height: none; overflow: visible; }
    .dv-printonly { display: block; }
    a { color: inherit; text-decoration: none; }
    body { background: #fff; }
}

/* Nombre de boitiers rattaches, en ovale gris. */
.bt-count {
    display: inline-block; min-width: 20px;
    padding: 1px 8px; border-radius: 999px;
    background: #e7e9ed; color: #4a5563;
    font-size: 10.5px; font-weight: 700; line-height: 1.5;
    text-align: center; vertical-align: middle;
    font-variant-numeric: tabular-nums;
}

/* Barre sous les tris : legende du lisere a gauche, interrupteur a droite. */
.client-toolbar {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    margin: 0 0 6px;
}
.legend-orphan {
    display: inline-flex; align-items: center; gap: 7px;
    margin-right: auto;
    font-size: 11.5px; color: var(--muted);
}
/* Reprend EXACTEMENT le lisere des lignes orphelines, pour qu'on fasse le lien. */
.legend-mark {
    display: inline-block; width: 4px; height: 15px;
    background: #c3cad3; border-radius: 2px; flex: none;
}
.client-sort a { margin: 0 4px; text-decoration: none; }
.client-sort a.active { font-weight: 700; text-decoration: underline; }

/* Catalogue : la barre « cocher des boitiers puis Louer la selection ». */
.rental-select-bar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 10px; }
.rental-select-all { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--muted); font-weight: 700; }
.rental-select-all input { margin: 0; }
.rental-select-bar .button[disabled] { opacity: 0.45; cursor: not-allowed; }

/* « Ou est ce boitier ? » : la recherche par numero, en tete du catalogue. */
.rental-find-form { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-bottom: 12px; }
.rental-find-form input[name="q"] { min-width: 240px; }

/* Le choix des boitiers a louer : une liste filtrable de cases a cocher. Elle
   defile dans sa propre boite pour qu'un parc de 200 numeros ne noie pas le
   reste du formulaire. */
.rental-picker { margin-bottom: 16px; }
.rental-picker-filters { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-bottom: 8px; }
.rental-picker-filters select, .rental-picker-filters input { min-width: 180px; }
.rental-picker-count { font-size: 11px; color: var(--muted); }
.rental-picker-list {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 6px; max-height: 320px; overflow-y: auto;
    border: 1px solid var(--primary); border-radius: 8px; padding: 8px;
}
.rental-pick {
    display: flex; align-items: center; gap: 8px; padding: 6px 8px;
    border: 1px solid var(--line); border-radius: 6px; cursor: pointer;
}
.rental-pick:hover { background: var(--primary-soft); }
.rental-pick[hidden] { display: none; }
.rental-pick input { margin: 0; flex: none; }
.rental-pick-serial { font-weight: 700; font-family: Consolas, "Courier New", monospace; }
.rental-pick-model { font-size: 11px; color: var(--muted); }
.rental-pick-loc { font-size: 10px; color: var(--muted); margin-left: auto; }

/* Le numero d'un boitier (type Axxxxx) se lit et se recopie : chasse fixe. */
.rental-serial { font-family: Consolas, "Courier New", monospace; user-select: all; }

/* Fiche d'un boitier : le boitier a gauche, sa situation a droite. */
.rm-top { display: grid; grid-template-columns: 1fr 1fr; gap: 5px; align-items: start; margin-bottom: 5px; }
.rm-photo { max-width: 100%; border-radius: 8px; margin-bottom: 8px; }
.rm-photo-small { max-width: 180px; }
@media (max-width: 900px) { .rm-top { grid-template-columns: 1fr; } }

/* ===== Fil d'activite (tableau de bord) ===== */
.activity-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.activity-actions { display: flex; gap: 8px; flex-wrap: wrap; }
/* Les activites lues sont masquees par defaut ; le bouton « Voir les lues » les revele. */
.activity-panel:not(.show-read) .activity-item.is-read { display: none; }
.activity-panel.show-read .activity-item.is-read { opacity: 0.62; }

/* Filtres par type d'activite.
   Le filtre est fait en CSS, comme le masquage des lues : les deux se COMBINENT
   alors tout seuls (une ligne lue ET d'un autre type reste cachee), sans que le JS
   ait a arbitrer entre les deux regles. */
.activity-filters { display: flex; flex-wrap: wrap; gap: 5px; margin: 0 0 10px; }
.activity-filter-n {
    display: inline-block;
    margin-left: 4px;
    padding: 0 5px;
    border-radius: 999px;
    background: rgba(0, 0, 0, .08);
    font-size: 9px;
    font-weight: 800;
}
.button.primary .activity-filter-n { background: rgba(255, 255, 255, .28); }
/* data-filter sur le panneau : on masque tout ce qui n'est pas du type demande. */
.activity-panel[data-filter]:not([data-filter="all"]) .activity-item { display: none; }
.activity-panel[data-filter="in"] .activity-item[data-type="in"],
.activity-panel[data-filter="out"] .activity-item[data-type="out"],
.activity-panel[data-filter="inventory"] .activity-item[data-type="inventory"],
.activity-panel[data-filter="build"] .activity-item[data-type="build"],
.activity-panel[data-filter="asm_out"] .activity-item[data-type="asm_out"],
.activity-panel[data-filter="sale"] .activity-item[data-type="sale"],
.activity-panel[data-filter="order"] .activity-item[data-type="order"] { display: flex; }
/* ... mais une ligne LUE reste cachee tant que « Voir les lues » est inactif. */
.activity-panel:not(.show-read) .activity-item.is-read { display: none; }
.activity-unread-badge { display: inline-block; margin-left: 8px; padding: 1px 9px; border-radius: 999px; background: var(--primary); color: #fff; font-size: 11px; font-weight: 800; vertical-align: middle; }
.activity-list { list-style: none; margin: 12px 0 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.activity-item { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 9px; border-left: 3px solid transparent; }
.activity-item.is-unread { background: var(--primary-soft); border-left-color: var(--primary); }
.activity-item.is-read { opacity: 0.62; }
.activity-dot { flex: 0 0 auto; width: 14px; height: 14px; border-radius: 50%; }
.activity-dot-in { background: #1c7a3f; }
.activity-dot-out { background: var(--danger); }
/* Inventaire : un constat, pas un flux -> ni vert, ni rouge. */
.activity-dot-adjust { background: #2d5c8a; }
/* Location : la pastille porte l'ETAT du contrat, pas un sens de flux. */
.activity-dot-rental-en_cours { background: var(--success); }
.activity-dot-rental-bientot { background: #f08c00; }
.activity-dot-rental-retard { background: var(--danger); }
.activity-dot-rental-rendue { background: #9aa5b1; }
.activity-who { flex: 0 0 auto; width: 26px; height: 26px; font-size: 10px; }
.activity-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.activity-text { font-weight: 700; font-size: 12.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.activity-item.is-unread .activity-text { color: var(--ink); }
/* Le libelle renvoie a sa source (vente, piece, assemblage, commande). */
a.activity-link { display: block; color: inherit; text-decoration: none; cursor: pointer; }
a.activity-link:hover { color: var(--primary-dark); text-decoration: underline; }
.activity-meta { font-size: 10.5px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.activity-mark { flex: 0 0 auto; }

/* Assemblages : realisables (bleu) + pieces manquantes (rose) */
.buildable-badge { color: #1763c2; font-weight: 800; font-size: 12px; margin: 0; }
.buildable-inline { display: inline-grid; place-items: center; min-width: 28px; height: 24px; padding: 0 8px; border-radius: 999px; background: #e6f0fb; color: #1763c2; }
.shortage-badge { color: var(--danger); font-weight: 800; font-size: 11px; margin: 2px 0 0; }
.item-card.assembly-shortage { border-color: #f3b6c2; }
.shortage-banner { background: #fdeaf0; border: 1px solid #f3b6c2; color: #a01a44; font-weight: 700; padding: 8px 12px; border-radius: 8px; margin: 0 0 12px; }
.assembly-part-list li.apl-missing { background: #fdeaf0; border-color: #f3b6c2; }

/* Schemas techniques : categories */
.schematic-cat-title { margin: 18px 0 10px; font-size: 14px; }
.schematic-cat-tag { display: inline-block; background: var(--primary-soft); color: var(--primary-dark); font-weight: 700; font-size: 10px; padding: 2px 8px; border-radius: 999px; margin: 0 0 4px; }

/* Pastilles de presence (utilisateurs connectes, couleur par utilisateur) */
.presence-bar { position: fixed; top: 12px; right: 18px; z-index: 75; display: flex; align-items: center; }
/* Zone haut-droite : utilisateurs connectes + petit bouton deconnexion a droite. */
.topbar-right { position: fixed; top: 12px; right: 18px; z-index: 75; display: flex; align-items: center; gap: 8px; }
.topbar-right .presence-bar { position: static; top: auto; right: auto; }
.topbar-logout-form { margin: 0; display: flex; }
.topbar-logout { display: grid; place-items: center; width: 30px; height: 30px; min-height: 0; padding: 0; border-radius: 8px;
    border: 1px solid var(--line); background: var(--card, #fff); color: var(--muted); cursor: pointer; }
.topbar-logout svg { width: 17px; height: 17px; }
.topbar-logout:hover { border-color: var(--danger); color: var(--danger); background: #fdecea; }
/* Boutons « Mon compte » + « Back-office » en haut a gauche de la zone de contenu. */
.account-quick-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-bottom: 4px; }
/* Pavés de synthèse (Pièces / Assemblages / Stock total) à droite du bouton Alertes :
   même « pilule » ovale que les boutons Mon compte / Back-office / Alertes. */
.account-quick-row .topstat {
    flex-direction: row; align-items: baseline; gap: 6px;
    padding: 4px 12px; border: 1px solid var(--line); border-radius: 999px; background: #fff;
}
.account-quick-row a.topstat:hover { border-color: var(--primary); }
.account-quick-row .topstat .topstat-label { font-size: 11.5px; font-weight: 700; }
.account-quick-row .topstat .topstat-value { font-size: 13px; }
/* Stock total (div, non cliquable) : rendu neutre, sans effet de survol. */
.account-quick-row div.topstat { cursor: default; }
/* Tous les boutons ovales de la ligne du haut : MEME hauteur (contenu centre). */
.account-quick-row > .account-quick,
.account-quick-row > .topstat {
    height: 30px;
    box-sizing: border-box;
    padding-top: 0;
    padding-bottom: 0;
    align-items: center;
}
.account-quick { display: inline-flex; align-items: center; gap: 7px; padding: 4px 10px 4px 4px;
    border: 1px solid var(--line); border-radius: 999px; background: var(--card, #fff); text-decoration: none;
    color: var(--ink); font-size: 11.5px; font-weight: 700; }
.account-quick:hover { border-color: var(--primary); background: var(--primary-soft); }
.account-quick.active { border-color: var(--primary); }
.account-quick .presence-badge { width: 24px; height: 24px; font-size: 10px; }
.account-quick .nav-icon { width: 20px; height: 20px; }
.account-quick .nav-icon svg { width: 18px; height: 18px; }
/* Bouton Alertes : rose, avec la pastille du nombre en haut a droite. */
.account-quick.account-alert { position: relative; background: #fbcfe0; border-color: #ec4899; color: #9d174d; padding-right: 12px; }
.account-quick.account-alert .nav-icon svg { color: #db2777; }
.account-quick.account-alert:hover { background: #f9a8c8; border-color: #db2777; }
.account-quick.account-alert.active { border-color: #db2777; box-shadow: 0 0 0 2px rgba(219, 39, 119, .25); }
.account-alert-badge { position: absolute; top: -6px; right: -6px; min-width: 18px; height: 18px; padding: 0 5px;
    display: inline-flex; align-items: center; justify-content: center; border-radius: 999px;
    background: #db2777; color: #fff; font-size: 10px; font-weight: 800; line-height: 1;
    box-shadow: 0 1px 3px rgba(0, 0, 0, .3); }
.presence-badge { width: 30px; height: 30px; border-radius: 50%; display: inline-grid; place-items: center; color: #3a3f46; font-weight: 800; font-size: 11px; border: none; box-shadow: 0 1px 5px rgba(0, 0, 0, 0.25); margin-left: 2px; }
.presence-badge:first-child { margin-left: 0; }
.presence-badge-static { margin-left: 0; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.18); }

/* Selecteur de couleur de badge (pastels) */
.badge-field { display: grid; gap: 8px; margin: 4px 0 2px; }
.badge-field-label { font-size: 12px; font-weight: 600; color: var(--muted); }
.badge-picker { display: flex; flex-wrap: wrap; gap: 8px; }
.badge-picker .swatch { cursor: pointer; line-height: 0; }
.badge-picker .swatch input { position: absolute; opacity: 0; width: 0; height: 0; }
.badge-picker .swatch-dot { display: inline-grid; place-items: center; width: 28px; height: 28px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.12); transition: transform .12s, box-shadow .12s; }
.badge-picker .swatch-dot-auto { background: repeating-conic-gradient(#e9edf2 0% 25%, #fff 0% 50%) 50% / 12px 12px; color: #3a3f46; font-weight: 800; font-size: 11px; }
.badge-picker .swatch:hover .swatch-dot { transform: scale(1.08); }
.badge-picker .swatch input:checked + .swatch-dot { box-shadow: 0 0 0 2px var(--primary); transform: scale(1.1); }

/* Page de connexion (login global) */
.login-page { display: grid; place-items: center; min-height: 100vh; background: var(--bg); }
/* Champ « client » : combo (saisie libre + liste deroulante filtrable). */
.combo { position: relative; display: block; }
.combo input { width: 100%; padding-right: 26px; }
.combo-toggle {
    position: absolute; right: 3px; top: 50%; transform: translateY(-50%);
    border: 0; background: none; color: var(--muted); cursor: pointer;
    padding: 4px 6px; line-height: 1; font-size: 12px;
}
.combo-toggle:hover { color: var(--ink); }
.combo-panel {
    position: absolute; z-index: 60; left: 0; right: 0; top: calc(100% + 3px);
    max-height: 264px; overflow-y: auto;
    display: flex; flex-direction: column;
    background: var(--surface, #fff); border: 1px solid var(--line); border-radius: 8px;
    box-shadow: 0 10px 26px rgba(20, 30, 45, .20);
}
.combo-panel[hidden] { display: none; }
.combo-opt {
    text-align: left; border: 0; background: none; font: inherit; cursor: pointer;
    padding: 6px 10px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    /* INDISPENSABLE : le panneau est un flex en colonne borne a 264px. Sans
       flex-shrink:0, les ~160 options sont ecrasees pour tenir dans cette hauteur
       et les libelles se chevauchent au lieu de faire defiler le panneau. */
    flex: 0 0 auto;
    min-height: 0;
    line-height: 1.35;
}
.combo-opt:hover, .combo-opt.is-active { background: #eaf1fb; }

/* Champ mot de passe avec bouton Afficher / Masquer. */
.pwd-wrap { position: relative; display: block; }
.pwd-wrap input { width: 100%; padding-right: 84px; }
.pwd-toggle {
    position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
    border: 1px solid #d6dae0; background: #f4f6f9; color: #333;
    border-radius: 6px; padding: 4px 9px; font-size: 12px; cursor: pointer; line-height: 1.2;
}
.pwd-toggle:hover { background: #e8ecf2; }
.pwd-toggle:focus-visible { outline: 2px solid #1d6fd0; outline-offset: 2px; }

.login-box { width: min(380px, 92vw); display: grid; gap: 18px; justify-items: center; }
.login-logo { width: 170px; max-width: 70%; height: auto; }
.login-box .form { width: 100%; }
.login-box h2 { margin: 0 0 6px; }

/* Cases a cocher / radios a taille normale (la regle input globale les gonflait) */
input[type="checkbox"], input[type="radio"] { width: auto; min-height: 0; margin: 0; accent-color: var(--primary); }
.checkbox-label { display: flex; flex-direction: row; align-items: center; gap: 8px; }

/* Formulaire utilisateur : compact et aere */
.user-form { max-width: 560px; }
.user-form .form-grid { grid-template-columns: 1fr 1fr; gap: 12px 14px; }
.user-form label { font-size: 10px; gap: 4px; }
.user-form input, .user-form select { min-height: 32px; padding: 5px 9px; font-size: 12px; }
.form-hint { font-size: 11px; margin: 10px 0 0; }

/* Etiquette de role */
.role-tag { display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: 10px; font-weight: 800; }
.role-super { background: #efe3fb; color: #6b21a8; }
.role-admin { background: #e6f0fb; color: #1763c2; }
.role-user { background: #eceff3; color: #5b6573; }

/* ===== Graphique d'evolution du stock (lignes de couleur uniquement) ===== */
.stock-trend-panel { margin-top: 24px; }
.stock-chart-wrap { width: 100%; overflow: hidden; }
.stock-chart { display: block; width: 100%; height: auto; }
.stock-chart .sc-line { fill: none; stroke: #2f6bd8; stroke-width: 2.2; stroke-linejoin: round; stroke-linecap: round; }
.stock-chart .sc-dot { fill: #2f6bd8; }
.stock-chart .sc-trend { stroke: #FF6B26; stroke-width: 2; stroke-dasharray: 7 5; }
.stock-chart .sc-threshold { stroke: #b42318; stroke-width: 1.6; stroke-dasharray: 4 4; }
.stock-chart .sc-threshold-dot { fill: #b42318; }
.stock-chart .sc-order { stroke: #197a50; stroke-width: 2; stroke-dasharray: 3 3; }
.stock-chart .sc-ylabel { fill: #687382; font-size: 10px; text-anchor: end; }
.stock-chart .sc-xlabel { fill: #687382; font-size: 10px; }
.stock-pred { margin-top: 12px; display: grid; gap: 6px; }
.sc-stat { margin: 0; font-size: 12px; }
.sc-stat-label { color: var(--muted); }
.sc-order-date strong { color: #197a50; }
.sc-order-date.is-late strong { color: #b42318; }
.sc-badge-late { display: inline-block; margin-left: 6px; padding: 1px 8px; border-radius: 999px; background: #fde7e3; color: #b42318; font-size: 10px; font-weight: 800; }

/* Sparkline dans les cartes de liste (pieces / assemblages) */
.card-spark { margin: 8px 0 2px; }
.card-spark .stock-spark { display: block; width: 100%; height: 40px; }
.card-spark .sc-line { fill: none; stroke: #2f6bd8; stroke-width: 1.6; vector-effect: non-scaling-stroke; }
.card-spark .sc-trend { stroke: #FF6B26; stroke-width: 1.4; stroke-dasharray: 5 4; vector-effect: non-scaling-stroke; }
.card-spark .sc-threshold { stroke: #b42318; stroke-width: 1.2; stroke-dasharray: 3 3; vector-effect: non-scaling-stroke; }
.card-spark .sc-dot { fill: #2f6bd8; }

/* ===== Renforcement responsive (tablettes / telephones) ===== */
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
img, svg, canvas, video { max-width: 100%; }
@media (max-width: 560px) {
    .main { padding: 56px 14px 16px; }
    nav { grid-template-columns: 1fr; }
    .presence-bar { position: static; justify-content: flex-end; margin-bottom: 6px; }
    /* En-tete encore plus compacte sur telephone (bouton inline, peu de marge). */
    .page-header { margin-bottom: 22px; gap: 6px 10px; }
    .page-header h1 { font-size: 16px; }
    .page-header .eyebrow { font-size: 8px; }
    .page-header .button { width: auto; min-height: 28px; padding: 4px 9px; font-size: 12px; }
    .form-actions .button, .form-actions { width: 100%; }
    .form-actions { display: flex; flex-direction: column; gap: 8px; }
    /* Champs de formulaire : 1 colonne sur telephone pour rester lisibles. */
    .user-form .form-grid, .part-form-main .form-grid { grid-template-columns: 1fr; }
    .grid-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    table { font-size: 12px; }
    /* Boutons des cartes : autorises a passer sur plusieurs lignes et reduits. */
    .item-card .actions { flex-wrap: wrap; gap: 6px; }
    .item-card .actions > * { flex: 1 1 auto; }
    .item-card .actions .button { font-size: 10.5px; min-height: 26px; padding: 3px 6px; }
    .badge-picker .swatch-dot { width: 32px; height: 32px; }
}

/* --- Pastilles de niveau d'acces (admin = cadenas, super = bouclier) --- */
.access-badge { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; border-radius: 50%; flex: none; vertical-align: middle; }
.access-badge svg { width: 12px; height: 12px; }
.access-badge-admin { color: #1763c2; background: rgba(23, 99, 194, .14); }
.access-badge-super { color: #b06a00; background: rgba(176, 106, 0, .16); }
/* En menu : pastille placee devant la ligne (petite marge a droite). */
/* Badge d'autorisation cadre completement a droite de la ligne de menu. */
.nav-item .access-badge { margin-left: auto; flex: none; }
.nav-parent .access-badge { margin-left: auto; flex: none; }
/* Coin superieur droit d'une carte / tuile. */
.access-corner { position: absolute; top: 8px; right: 8px; z-index: 2; }
.bo-tile { position: relative; }

/* --- Pages Firestore (lecture base KIOMDA) --- */
.firestore-pager { display: flex; gap: 10px; align-items: center; margin-top: 14px; }
.firestore-pager .muted { font-size: 12px; }
/* Fiche detail : cle a gauche, valeur a droite. */
table.firestore-detail { width: 100%; border-collapse: collapse; }
table.firestore-detail th { text-align: left; white-space: nowrap; vertical-align: top; width: 220px; }
table.firestore-detail td { vertical-align: top; }
table.firestore-detail tr { border-bottom: 1px solid rgba(0,0,0,.06); }
table.firestore-detail th, table.firestore-detail td { padding: 5px 14px 5px 0; }
/* Lignes a 2 champs : separateur vertical avant le 2e champ. */
table.firestore-detail td + th { border-left: 2px solid rgba(0,0,0,.14); padding-left: 16px; }
/* Arborescence des valeurs imbriquees (tableaux / maps). */
.firestore-tree { margin: 0; padding-left: 18px; }
.firestore-tree li { margin: 2px 0; }
/* Bandeau de synthèse en tête d'une fiche (commande...). */
.fs-summary { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; margin: 0 0 16px; padding: 12px 14px; background: rgba(23,99,194,.05); border: 1px solid rgba(23,99,194,.12); border-radius: 10px; }
.fs-summary-title { font-weight: 700; font-size: 14px; }
.fs-summary-item { font-size: 13px; color: #34404d; }
/* Vue par client : un bloc par commande avec sa table de compteurs. */
.firestore-order { margin: 16px 0; padding-top: 12px; border-top: 1px solid rgba(0,0,0,.08); }
.firestore-order h3 { margin: 0 0 8px; font-size: 14px; }
.firestore-order h3 .muted { font-weight: 400; font-size: 12px; }
/* Encart position (fiche detail compteur). */
.firestore-geo { margin: 0 0 16px; padding: 12px 14px; border: 1px solid rgba(23,99,194,.2); background: rgba(23,99,194,.05); border-radius: 8px; }
.firestore-geo h2 { margin: 0 0 6px; font-size: 14px; }
/* Histogramme des comptages (page Comptages). */
.counts-chart-box { margin: 8px 0 18px; }
.counts-gran { display: flex; gap: 6px; margin-bottom: 8px; flex-wrap: wrap; }
.counts-gran button.active { background: #1763c2; color: #fff; }
.counts-nav { display: flex; gap: 8px; align-items: center; margin-bottom: 10px; flex-wrap: nowrap; }
.counts-nav input[type="date"] { padding: 4px 8px; min-width: 0; flex: 1 1 0; }
.counts-totals { margin: 8px 0 14px; font-size: 14px; text-align: center; }
.counts-totals strong { font-size: 18px; font-weight: 800; }

/* Boutons de selection de pratique (couleur = entrée de la pratique). */
.counts-practice { display: flex; gap: 6px; margin-bottom: 8px; flex-wrap: wrap; align-items: center; }
.counts-practice .muted { margin-right: 2px; }
.fs-prac-btn { color: var(--in); border: 1.5px solid var(--in); font-weight: 600; background: #fff; }
.fs-prac-btn:hover { background: color-mix(in srgb, var(--in) 12%, #fff); }
.fs-prac-btn.active { background: var(--in); color: #fff; border-color: var(--in); }
.counts-chart-canvas { position: relative; height: 340px; }

/* Tableau de bord Firestore. */
.fs-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; }
.fs-kpis article { background: rgba(23,99,194,.06); border: 1px solid rgba(23,99,194,.15); border-radius: 10px; padding: 12px 14px; display: flex; flex-direction: column; gap: 4px; }
.fs-kpis article span { font-size: 11.5px; color: #5b6470; }
.fs-kpis article strong { font-size: 16px; line-height: 1; }
.dash-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 16px; }
.dash-grid h3 { font-size: 11.5px; color: #5b6470; margin: 12px 0 6px; text-transform: uppercase; letter-spacing: .03em; }
.fs-bars { display: flex; flex-direction: column; gap: 6px; }
.fs-bar-row { display: grid; grid-template-columns: 150px 1fr 52px; align-items: center; gap: 8px; font-size: 12px; }
.fs-bar-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fs-bar-track { background: rgba(0,0,0,.06); border-radius: 6px; height: 14px; overflow: hidden; }
.fs-bar-fill { display: block; height: 100%; background: #1763c2; border-radius: 6px; }
.fs-bar-val { text-align: right; font-weight: 600; }

/* Badges statut / type. */
.fs-badge { display: inline-block; padding: 2px 10px; border-radius: 12px; font-size: 11.5px; font-weight: 600; white-space: nowrap; }
.fs-badge-neutral { color: #34404d; background: rgba(0,0,0,.07); }
a.fs-badge { text-decoration: none; }
.fs-practices { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.fs-mob-icon { width: 18px; height: 18px; vertical-align: middle; margin-right: 4px; }
/* Booleens graphiques. */
.fs-bool { display: inline-block; padding: 2px 9px; border-radius: 12px; font-size: 11.5px; font-weight: 600; }
.fs-bool-on { color: #1c7a3f; background: rgba(28,122,63,.12); }
.fs-bool-off { color: #5b6470; background: rgba(91,100,112,.12); }

/* Jours de la semaine (excluded_days). */
.fs-weekdays { display: flex; flex-wrap: wrap; gap: 6px; }
.fs-day { display: inline-flex; align-items: center; gap: 4px; padding: 3px 9px; border-radius: 14px; font-size: 11.5px; font-weight: 600; }
.fs-day-on { background: rgba(28, 122, 63, .12); color: #1c7a3f; }
.fs-day-off { background: rgba(192, 57, 43, .12); color: #c0392b; }
/* Jauges (gauge_tracking_levels). */
table.fs-gauges { border-collapse: collapse; font-size: 12px; }
table.fs-gauges th, table.fs-gauges td { text-align: left; padding: 4px 12px 4px 0; border-bottom: 1px solid rgba(0,0,0,.06); }
.fs-swatch { display: inline-block; width: 13px; height: 13px; border-radius: 3px; vertical-align: middle; border: 1px solid rgba(0,0,0,.2); }

/* Séparation entre groupes de boîtiers (pratiques d'un même compteur physique). */
tr.fs-grp > td { border-top: 3px solid rgba(0,0,0,.30); }

/* Association compteurs <-> canaux (fiche commande). */
table.fs-assoc { border-collapse: collapse; font-size: 12px; margin-top: 6px; }
table.fs-assoc th, table.fs-assoc td { text-align: left; padding: 6px 16px 6px 0; border-bottom: 1px solid rgba(0,0,0,.08); vertical-align: top; }
table.fs-assoc th { font-weight: 600; }
table.fs-assoc td:first-child { white-space: nowrap; }

/* Logs decomposes en tableau (fiche detail). */
table.firestore-logs { width: 100%; border-collapse: collapse; font-size: 11.5px; }
table.firestore-logs th, table.firestore-logs td { text-align: left; padding: 3px 8px; border-bottom: 1px solid rgba(0,0,0,.06); vertical-align: top; white-space: nowrap; }
table.firestore-logs td:last-child { white-space: normal; }

/* Petite carte des positions GPS des capteurs d'un client. */
.client-map { height: 320px; border-radius: 8px; overflow: hidden; margin: 4px 0 16px; border: 1px solid rgba(0,0,0,.12); }
.client-map .leaflet-popup-content { font-size: 12px; }
/* Pin de carte uniforme : petit rond, centre gris, pourtour orange. */
.fs-pin { width: 14px; height: 14px; border-radius: 50%; background: #9aa0a6; border: 3px solid #f0902a; box-sizing: border-box; box-shadow: 0 0 0 1px rgba(0,0,0,.25); }
/* Sélecteur de client filtrable en direct. */
.firestore-client-picker { margin-bottom: 14px; }
.firestore-client-picker #fs-client-filter { width: 100%; max-width: 420px; }
.firestore-client-list { list-style: none; margin: 8px 0 0; padding: 4px; max-height: 280px; overflow-y: auto; border: 1px solid rgba(0,0,0,.12); border-radius: 8px; }
.firestore-client-list li { margin: 0; }
.firestore-client-list a { display: block; padding: 6px 10px; border-radius: 6px; text-decoration: none; color: inherit; }
.firestore-client-list a:hover { background: rgba(0,0,0,.05); }
.firestore-client-list a.active { background: rgba(23,99,194,.12); font-weight: 600; }
/* Indicateur de chargement (vue par client). */
.fs-loading { display: flex; align-items: center; justify-content: center; gap: 18px; padding: 56px 4px; color: #34404d; font-size: 24px; font-weight: 600; }
/* L'attribut [hidden] doit l'emporter sur le display:flex ci-dessus, sinon le
   spinner resterait visible en permanence. */
.fs-loading[hidden] { display: none; }
.fs-spinner { width: 44px; height: 44px; border: 5px solid rgba(0,0,0,.15); border-top-color: #1763c2; border-radius: 50%; animation: fs-spin .7s linear infinite; }
@keyframes fs-spin { to { transform: rotate(360deg); } }

/* ==================== EVOLUTION DU PARC LOCATIF (graphes) ==================== */
/* Cartes d'indicateurs en tete de la page. */
.stat-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; }
.stat-cards .stat-card { text-align: center; padding: 10px 8px; }
.stat-cards .stat-num { display: block; font-size: 22px; font-weight: 800; line-height: 1.1; color: var(--text); }
.stat-cards .stat-lbl { display: block; margin-top: 3px; font-size: 10px; font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: .02em; }
.stat-card.stat-loc { border-color: #1763c2; }
.stat-card.stat-loc .stat-num { color: #1763c2; }
.stat-card.stat-pret { border-color: #1c7a3f; }
.stat-card.stat-pret .stat-num { color: #1c7a3f; }

/* En-tete de panneau : titre a gauche, legende a droite. */
.panel-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.chart-legend { display: flex; gap: 14px; flex-wrap: wrap; }
.chart-key { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; font-weight: 700; color: var(--muted); }
.chart-key i { width: 12px; height: 12px; border-radius: 3px; display: inline-block; }

/* Le SVG lui-meme : responsive, defile horizontalement si tres etroit. */
.chart-wrap { overflow-x: auto; margin-top: 8px; }
.chart { width: 100%; height: auto; min-width: 520px; display: block; }
.chart-tick { fill: var(--muted); font-size: 11px; font-family: inherit; }
.chart-tick-year { fill: var(--muted); opacity: .7; }
.chart-val { fill: #0f2740; font-size: 12px; font-weight: 800; font-family: inherit; }

/* ==================== JOURNAL DES DERNIERES OPERATIONS ==================== */
/* Puces de filtre par type d'operation (compteurs inclus). */
.op-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.op-chip { display: inline-flex; align-items: center; gap: 6px; padding: 5px 11px; border: 1px solid var(--line); border-radius: 999px; font-size: 11px; font-weight: 700; color: var(--text); text-decoration: none; background: var(--surface); }
.op-chip:hover { border-color: var(--primary); }
.op-chip.active { border-color: var(--primary); background: var(--primary); color: #fff; }
.op-chip-n { font-size: 10px; font-weight: 800; padding: 1px 6px; border-radius: 999px; background: rgba(0,0,0,.10); }
.op-chip.active .op-chip-n { background: rgba(255,255,255,.25); }
/* Marqueur discret « en maintenance » sur une ligne du journal. */
.op-inmaint { display: inline-block; margin-left: 6px; padding: 1px 7px; border-radius: 999px; font-size: 9.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .02em; color: #b06a00; background: #fff3e0; border: 1px solid #f0c98a; }
.ops-table td { vertical-align: middle; }

/* ============ SUIVI DE RETOUR DES BOITIERS DEHORS (traitement + colis) ============ */
/* Pastille de statut de traitement (relance / renvoi prevu / recu…). */
.treat-badge { display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: 10.5px; font-weight: 800; color: #fff; }
.treat-note { margin-top: 3px; font-size: 10.5px; color: var(--muted); }
.treat-cell { min-width: 210px; }
.treat-parcel { margin-top: 6px; display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.treat-parcel-num { font-size: 10px; }
.treat-parcel [data-parcel-status] { font-size: 10.5px; }

/* Editeur « Traiter le retour » : blocs de formulaire encadres. */
.stacked-form .form-block { border: 1px solid var(--line); border-radius: 8px; padding: 12px 14px 14px; margin-bottom: 14px; }
.stacked-form .form-block > legend { padding: 0 6px; font-size: 11px; font-weight: 800; color: var(--primary); text-transform: uppercase; letter-spacing: .03em; }
.form-wide { grid-column: 1 / -1; }

/* Compteur dans les onglets de filtre Locations / Prêts. */
.tab-count { display: inline-block; min-width: 16px; padding: 0 5px; margin-left: 2px; border-radius: 999px; font-size: 10px; font-weight: 800; background: rgba(0,0,0,.10); }
.button.primary .tab-count { background: rgba(255,255,255,.28); }

/* Ville d'un boîtier de location déployé (là où il est censé se trouver). */
.loc-city { display: inline-block; font-weight: 700; color: #0d7d8c; white-space: nowrap; }

/* Note « suivis actualisés automatiquement à HH:MM » (liste des ventes). */
.track-auto-note { margin: 2px 0 8px; font-size: 11px; }

/* Barre « Tout déplier / Tout replier » du menu latéral. */
.nav-toolbar { display: flex; gap: 6px; margin: 2px 0 6px; }
.nav-toolbtn {
    flex: 1 1 0; display: inline-flex; align-items: center; justify-content: center; gap: 5px;
    padding: 4px 6px; font-size: 9.5px; font-weight: 800; font-family: inherit;
    text-transform: uppercase; letter-spacing: .03em; color: var(--primary-dark);
    background: #FFEFE4; border: 1px solid #f0c9ac; border-radius: 7px; cursor: pointer;
}
.nav-toolbtn:hover { background: #FFE3D2; border-color: var(--primary); }
.nav-toolbtn:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
.nav-toolico { font-size: 12px; line-height: 1; }

/* Nom(s) de l'assemblage / pièce dans le résumé d'un mouvement (avant d'ouvrir). */
.mg-items { flex: 1 1 auto; min-width: 0; font-weight: 700; font-size: 12px; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.movement-group .mg-summary { flex: 0 0 auto; }

/* Écran de réconciliation des noms clients. */
.reconcile-table td { vertical-align: middle; }
.reconcile-where { display: flex; flex-wrap: wrap; gap: 5px; }
.reconcile-tag { display: inline-block; padding: 1px 8px; border-radius: 999px; font-size: 10px; font-weight: 700; color: var(--muted); background: var(--primary-soft); border: 1px solid var(--line); }
.reconcile-tag b { color: var(--text); }
.reconcile-form { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin: 0; }
.reconcile-form .combo { flex: 1 1 220px; min-width: 180px; }
