/* ==========================================================================
   SoftiField v2 - styles propres a l'application
   --------------------------------------------------------------------------
   1. Shell commun (js/shell.js) : grille topbar + contenu + sidebar a droite,
      tiroir sous 1024 px.
   2. Page de connexion (login.html, maquette D1).
   3. Tableau de bord (index.html, maquette D2) : carte live, panneaux glass.
   4. Modulation (modulation.html) : meme carte, panneau C7 de dessin.
   5. Parametres (parametres.html) : cartes C5 palette, camera, compte.
   6. Champs (champs.html) : liste de cartes C6, carte, editeur de contour et
      import guide.
   7. Administration (admin.html) : etat du systeme, comptes, nouveau compte.
   Dependances : tokens.css, components.css (et leaflet.css pour les sections
   3, 4 et 6).
   ========================================================================== */

/* ==========================================================================
   1. SHELL
   --------------------------------------------------------------------------
      +------------------------------+-----------+
      | topbar (fil d'Ariane, theme) |  sidebar  |
      +------------------------------+  logo     |
      | <main> de la page            |  nav      |
      |                              |  compte   |
      +------------------------------+-----------+
   Le <main> enfant direct de <body> occupe la zone de contenu. Tout ce que
   les scripts ajoutent a <body> (modal, toast, lien de telechargement) doit
   etre en position fixe ou absolue, sinon la grille lui donnerait une case.
   ========================================================================== */
/* Fond du canevas du navigateur (rebond de defilement, zone hors <body>) :
   celui de l'application. La base de la charte pose --bg-2 sur <html>, plus
   clair que --app-canvas en sombre : bande claire au rebond sur mobile. */
html { background: var(--app-canvas); }

.app-shell {
    /* Largeur reelle de la sidebar : --sidebar-w deployee, remplacee par
       --sidebar-w-collapsed quand elle est retractee (plus bas). Tout ce qui
       depend de la colonne lit cette variable, jamais les jetons. */
    --shell-sidebar-w: var(--sidebar-w);
    display: grid;
    grid-template-columns: minmax(0, 1fr) var(--shell-sidebar-w);
    grid-template-rows: var(--topbar-h) minmax(0, 1fr);
    grid-template-areas:
        "topbar sidebar"
        "main   sidebar";
    height: 100vh;
    height: 100dvh;
    overflow: hidden;
    background: var(--app-canvas);
}

.app-shell > main {
    grid-area: main;
    min-width: 0;
    min-height: 0;
    position: relative;
    overflow: auto;
    /* Leaflet empile ses panneaux jusqu'a z-index 1000 : isoler la page evite
       que la carte passe au-dessus du tiroir, des modals et des toasts. */
    isolation: isolate;
}
/* Cible du lien d'evitement (tabindex -1) : un cadre autour de toute la carte
   n'apprendrait rien, le focus clavier suivant se voit deja */
.app-shell > main:focus { outline: none; }

/* ---------- Topbar (C1) ---------- */
.app-topbar {
    grid-area: topbar;
    position: relative;
    z-index: var(--z-topbar);
    /* La charte montre la topbar seule, en carte : ici elle est posee bord a
       bord, un simple filet la separe du contenu. */
    display: flex;
    border: none;
    border-bottom: 1px solid var(--sidebar-line);
    border-radius: 0;
}
.app-topbar .crumb-row { flex: 1; }
.app-topbar .crumb--current {
    color: var(--sidebar-text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.app-topbar__actions {
    display: flex;
    align-items: center;
    gap: 8px;
}
.app-burger { display: none; }

/* ---------- Sidebar (C2) ---------- */
.app-sidebar {
    grid-area: sidebar;
    display: flex;
    flex-direction: column;
    min-height: 0;
    background: var(--sidebar-bg);
    color: var(--sidebar-text);
    border-left: 1px solid var(--sidebar-line);
}
.app-sidebar__head {
    height: var(--topbar-h);
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 0 16px;
    border-bottom: 1px solid var(--sidebar-line);
}
.app-sidebar__brand {
    display: flex;
    align-items: center;
    min-height: var(--tap);
    border-radius: var(--r-sm);
}
.app-logo { height: 40px; width: auto; }
/* Theme sombre : methode de la charte (logo passe en blanc) */
[data-theme="dark"] .app-logo { filter: brightness(0) invert(1); }
/* Pictogramme : colonne retractee seulement (plus bas) */
.app-logo--mark { display: none; }
.app-sidebar__close,
.app-sidebar__theme { display: none; }
/* Bascule de theme du tiroir : a droite, contre le bouton Fermer */
.app-sidebar__theme { margin-left: auto; }

/* Bascule de la sidebar (desktop), sous la tete et hors de la liste qui
   defile. Calee a droite (padding 10) : centre du bouton a 32 px du bord de
   la fenetre dans les deux etats, comme les icones de la colonne retractee.
   Placee dans la topbar, elle suivait le bord gauche de la colonne et
   quittait le pointeur a chaque clic. Chevron tourne vers le bord pour
   retracter, retourne pour deployer. Masquee sous 1024 px (burger). */
.app-sidebar__bascule {
    flex-shrink: 0;
    display: flex;
    justify-content: flex-end;
    padding: 8px 10px 0;
}
.app-sidebar-toggle {
    background: transparent;
    border-color: var(--sidebar-line);
    color: var(--sidebar-text);
}
.app-sidebar-toggle:hover { background: var(--sidebar-bg-2); }
.app-sidebar-toggle svg { transition: transform 0.16s ease; }
.is-sidebar-collapsed .app-sidebar-toggle svg { transform: rotate(180deg); }

.app-sidebar__nav {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    /* .nav-list est dessinee en carte dans la charte ; ici elle remplit la
       colonne. Le padding de 8 px place le marqueur ble sur le bord exterieur. */
    width: auto;
    border: none;
    border-radius: 0;
    background: transparent;
}
.app-sidebar .nav-section__label ~ .nav-section__label { margin-top: 8px; }
.app-sidebar .nav-item:hover { background: var(--sidebar-bg-2); }
/* DATUM : meme barre a gauche + tint leger que .nav-item.is-active (components).
   La barre ::before est heritee ; ici on cale juste fond et encre sur la
   sidebar. Vaut pour la sidebar de l'application ET la sidebar admin dediee. */
.app-sidebar .nav-item.is-active { background: var(--brand-softer); color: var(--brand-ink); }
/* Pendant la bascule, la colonne passe par toutes les largeurs : le libelle
   est coupe au lieu de passer a la ligne, la liste ne defile pas en largeur */
.app-sidebar__nav { overflow-x: hidden; }
.app-sidebar .nav-item__label {
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
}
/* Separateur de la sidebar admin, avant « Retour a l'application ». Filet net
   (--line-strong : --sidebar-line fait 1,2:1 en sombre), garde dans la colonne
   retractee (marges alignees sur les icones). */
.app-sidebar__nav .nav-sep {
    height: 1px;
    margin: 8px 12px;
    background: var(--line-strong);
}

/* Sidebar de 192 px : marges et ecart resserres pour laisser 79 px au nom
   (avatar 36 + deconnexion 44, bordure gauche 1). Le nom passe en 13 px et
   sur deux lignes au plus : un title ne s'affiche ni au doigt ni au clavier,
   l'identifiant doit se lire en entier dans la colonne. */
.app-sidebar__user {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 12px 4px 12px 12px;
    border-top: 1px solid var(--sidebar-line);
}
.app-sidebar__who {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
}
.app-sidebar__role {
    font-family: var(--mono);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    /* --sidebar-muted : 3,8:1 sur --sidebar-bg clair */
    color: var(--muted-ink);
}
/* Role du compte, pose par shell.js (data-role) a la place de « Compte » :
   badge mono sans pastille, qui tient dans les 79 px de la colonne
   (« OPERATEUR » : 72 px) */
.app-sidebar__role[data-role] {
    align-self: flex-start;
    margin-bottom: 2px;
    padding: 1px 6px;
    border-radius: var(--r-sm);
    background: var(--sidebar-bg-2);
    line-height: 16px;
}
/* --brand-ink sur --brand-soft : 5,4:1 en clair, 6:1 en sombre (tokens.css) */
.app-sidebar__role[data-role="admin"] {
    background: var(--brand-soft);
    color: var(--brand-ink);
}
.app-sidebar__name {
    font-family: var(--display);
    font-size: 13px;
    font-weight: 700;
    letter-spacing: -0.01em;
    line-height: 1.3;
    overflow: hidden;
    overflow-wrap: anywhere;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
}
.app-sidebar__logout {
    background: transparent;
    border-color: var(--sidebar-line);
    color: var(--sidebar-muted);
}
/* Deconnexion : --danger, conformement a la charte (A3) */
.app-sidebar__logout:hover {
    background: var(--danger-soft);
    border-color: var(--danger);
    color: var(--danger);
}

/* ---------- Lien d'evitement (clavier) ---------- */
.skip-link {
    position: fixed;
    top: 8px;
    left: 8px;
    z-index: var(--z-toast);
    padding: 8px 16px;
    background: var(--brand-fill);
    color: var(--on-brand);
    border-radius: var(--r-md);
    font-weight: 600;
    transform: translateY(-200%);
}
.skip-link:focus { transform: none; }

/* Toasts : au-dessus du contenu, pas sur la sidebar */
.app-shell .toasts { right: calc(var(--shell-sidebar-w) + 16px); }

/* ---------- Sidebar retractee (> 1024 px) ----------
   body.is-sidebar-collapsed, posee par shell.js avant l'insertion de la
   sidebar (etat par defaut, choix memorise) : colonne d'icones de
   --sidebar-w-collapsed. Sous 1024 px la classe reste posee mais ces regles
   ne s'appliquent pas : le tiroir garde logo, libelles et pied complets.
   « not all and » : complement exact du @media du tiroir, sans trou entre
   1024 et 1025 px (zoom). Les libelles restent lus par les lecteurs d'ecran
   (texte masque visuellement, meme technique que .sr-only) et s'affichent en
   bulle au survol et au focus clavier.
   Rien ne saute a la bascule, meme au debut de la transition : la bascule
   garde sa place, les icones leur distance au bord gauche de la colonne et
   leur hauteur (titres de section de meme hauteur), l'avatar sa distance au
   bord. Au deploiement, logo, titres et pied deployes sont decouverts par
   la colonne qui s'elargit (0,16 s, overflow: hidden). */
@media not all and (max-width: 1024px) {
    .app-shell {
        transition: grid-template-columns 0.16s ease;
    }
    .app-shell .toasts { transition: right 0.16s ease; }
    .app-shell.is-sidebar-collapsed { --shell-sidebar-w: var(--sidebar-w-collapsed); }
    /* En cours de transition, logo et pied deployes depassent de la colonne
       encore etroite : coupes, sinon la page deborde en largeur */
    .app-sidebar { overflow: hidden; }

    .is-sidebar-collapsed .app-sidebar__head {
        justify-content: center;
        padding: 0;
    }
    .is-sidebar-collapsed .app-sidebar__brand {
        min-width: var(--tap);
        justify-content: center;
    }
    .is-sidebar-collapsed .app-logo--full { display: none; }
    .is-sidebar-collapsed .app-logo--mark { display: block; }

    /* Entree : pilule de 47 px, padding de la colonne deployee garde (8 12).
       Le libelle sorti du flux ne compte plus dans le gap : l'icone est a
       12 px du bord gauche et 13 px du droit, centree a 0,5 px pres, sans
       saut au debut de la retraction (un centrage la placerait au milieu
       de l'entree encore large). La barre DATUM de l'entree active (::before)
       est a gauche (left: 0), sur le bord interieur : elle reste visible en
       colonne retractee et ne pousse pas l'icone (position absolue). */
    /* Icone centree dans la colonne retractee (independant de sa taille) */
    .is-sidebar-collapsed .app-sidebar .nav-item { justify-content: center; padding-left: 0; padding-right: 0; }

    .is-sidebar-collapsed .app-sidebar .nav-item__label,
    .is-sidebar-collapsed .app-sidebar__who {
        position: absolute;
        width: 1px; height: 1px;
        padding: 0; margin: -1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
        border: 0;
    }
    /* Titre de section : meme boite que deployee (les icones ne montent pas
       a la bascule), texte transparent coupe mais lu par les lecteurs
       d'ecran, filet de 1 px centre entre les deux groupes (marge de 8 px
       au-dessus, padding 8 / 4 : 4 px au-dessus du milieu de la boite).
       --line-strong : --sidebar-line fait 1,2:1 en sombre, le filet est le
       seul signe du regroupement. Le premier, sous la tete, n'a pas de filet. */
    .is-sidebar-collapsed .app-sidebar .nav-section__label {
        position: relative;
        overflow: hidden;
        white-space: nowrap;
        color: transparent;
    }
    .is-sidebar-collapsed .app-sidebar .nav-section__label::after {
        content: "";
        position: absolute;
        left: 4px;
        right: 4px;
        top: calc(50% - 4px);
        height: 1px;
        background: var(--line-strong);
    }
    .is-sidebar-collapsed .app-sidebar .nav-section__label:first-child::after { content: none; }

    /* Pied : avatar puis deconnexion, empiles et cales comme les icones
       (centres a 32 px du bord) : avatar a 13 px du bord gauche de la
       colonne, deconnexion a 9 px. L'avatar ne saute pas au debut de la
       retraction (12 px en deploye). */
    .is-sidebar-collapsed .app-sidebar__user {
        flex-direction: column;
        align-items: flex-start;
        gap: 8px;
        padding: 12px 0 12px 13px;
    }
    .is-sidebar-collapsed .app-sidebar__logout { margin-left: -4px; }

    /* Bulles de la colonne retractee : libelle d'une entree survolee ou
       focalisee au clavier, role et identifiant du compte au survol du pied,
       au toucher de l'avatar ou au focus de la deconnexion (un title ne
       s'affiche ni au doigt ni au clavier). Position fixe : la sidebar
       (overflow: hidden) et la liste qui defile couperaient une bulle
       absolue. Aucun top : position statique, centree sur l'entree
       (align-items) ou calee en haut du pied, a gauche de la colonne.
       Surface du tiroir : fond de la sidebar, --pop-line, --shadow-pop. */
    .is-sidebar-collapsed .app-sidebar .nav-item:hover .nav-item__label,
    .is-sidebar-collapsed .app-sidebar .nav-item:focus-visible .nav-item__label,
    .is-sidebar-collapsed .app-sidebar__user:hover .app-sidebar__who,
    .is-sidebar-collapsed .app-sidebar__user:focus-within .app-sidebar__who {
        position: fixed;
        right: calc(var(--shell-sidebar-w) + 8px);
        z-index: var(--z-tooltip);
        width: auto;
        height: auto;
        margin: 0;
        clip: auto;
        overflow: visible;
        padding: 8px 12px;
        background: var(--sidebar-bg);
        color: var(--sidebar-text);
        border: 1px solid var(--pop-line);
        border-radius: var(--r-md);
        box-shadow: var(--shadow-pop);
        font-weight: 600;
        pointer-events: none;
    }
    .is-sidebar-collapsed .app-sidebar__user:hover .app-sidebar__who,
    .is-sidebar-collapsed .app-sidebar__user:focus-within .app-sidebar__who {
        max-width: 200px;
        white-space: normal;
    }
}

/* ---------- Tiroir <= 1024 px ----------
   Meme seuil que MOBILE dans js/shell.js (qui ferme le tiroir quand on
   repasse au-dessus). Un seul seuil pour tout le shell. */
.app-backdrop {
    position: fixed;
    inset: 0;
    z-index: var(--z-backdrop);
    background: rgba(0, 0, 0, 0.45);
    animation: sf-fondu 0.12s ease-out;
}

@media (max-width: 1024px) {
    .app-shell {
        grid-template-columns: minmax(0, 1fr);
        grid-template-areas:
            "topbar"
            "main";
    }
    .app-shell .toasts { right: 16px; }
    .app-sidebar__bascule { display: none; }
    .app-burger,
    .app-sidebar__close,
    .app-sidebar__theme { display: inline-flex; }

    .app-sidebar {
        grid-area: auto;
        position: fixed;
        top: 0;
        right: 0;
        bottom: 0;
        width: min(320px, 85vw);
        z-index: var(--z-drawer);
        /* Surface posee : en sombre, le fond --sidebar-bg se confond avec la
           topbar assombrie par le voile, la bordure --pop-line la detache */
        border-left-color: var(--pop-line);
        box-shadow: var(--shadow-pop);
        /* visibility : tiroir ferme hors du parcours clavier et lecteur d'ecran */
        transform: translateX(100%);
        visibility: hidden;
        transition: transform 0.22s ease, visibility 0s linear 0.22s;
    }
    .app-shell.is-drawer-open .app-sidebar {
        transform: none;
        visibility: visible;
        transition: transform 0.22s ease;
    }
    /* Tiroir de 320 px : le pied reprend l'air que la colonne de 192 px lui
       retire, aligne sur la tete (16 px) */
    .app-sidebar__user {
        gap: 12px;
        padding: 12px 8px 12px 16px;
    }
    .app-sidebar__name { font-size: 15px; }
}

@media (max-width: 480px) {
    .app-topbar { padding: 0 16px; gap: 12px; }
    .app-topbar .crumb,
    .app-topbar .crumb-sep { display: none; }
    .app-topbar .crumb.crumb--current { display: block; }
}

@media (prefers-reduced-motion: reduce) {
    .app-sidebar,
    .app-shell.is-drawer-open .app-sidebar,
    .app-shell,
    .app-shell .toasts,
    .app-sidebar-toggle svg { transition: none; }
    .app-backdrop { animation: none; }
}

/* ==========================================================================
   2. CONNEXION (login.html, maquette D1)
   Carte de 480 px centree sur un halo --brand-soft.
   ========================================================================== */
.login-page {
    min-height: 100vh;
    min-height: 100dvh;
    background:
        radial-gradient(circle at 25% 30%, var(--brand-soft) 0%, transparent 45%),
        var(--app-canvas);
}
.login {
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 72px 16px 32px;
}
.login__theme {
    position: fixed;
    top: 16px;
    right: 16px;
}
.login__card {
    width: 100%;
    max-width: 480px;
    padding: 40px;
    display: flex;
    flex-direction: column;
    gap: 20px;
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-card);
}
.login__logo {
    height: 64px;
    width: auto;
    align-self: center;
}
.login__head { text-align: center; }
.login__title {
    font-family: var(--display);
    font-size: 28px;
    font-weight: 700;
    letter-spacing: -0.025em;
    line-height: 1.1;
}
.login__sub {
    margin-top: 8px;
    font-family: var(--mono);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    /* ECART : --ink-soft et non --muted (3,8:1), comme .field__label */
    color: var(--ink-soft);
}
.login__card .field { max-width: none; }

/* Zone d'erreur : vide et masquee tant que login.js n'y ecrit rien (.show) */
.login__error {
    display: none;
    padding: 12px 16px;
    background: var(--danger-soft);
    border-left: 4px solid var(--danger);
    border-radius: var(--r-md);
    /* Texte en --ink : --danger sur sa propre teinte ne fait que 3,8:1 en
       clair. Le lisere et le fond portent la couleur d'erreur. */
    color: var(--ink);
    font-size: 14px;
    font-weight: 600;
}
.login__error.show { display: block; }

.login__souvenir {
    align-self: flex-start;
    font-size: 14px;
}

/* Bouton oeil : une icone par etat, aria-pressed porte l'etat */
.field__eye-off { display: none; }
[aria-pressed="true"] > .field__eye { display: none; }
[aria-pressed="true"] > .field__eye-off { display: inline-block; }

@media (max-width: 480px) {
    .login { align-items: flex-start; }
    .login__card { padding: 24px 16px; }
}

/* ==========================================================================
   3. TABLEAU DE BORD (index.html, maquette D2)
   --------------------------------------------------------------------------
   Carte plein cadre (satellite seul) dans le <main> du shell, boites glass
   par-dessus.

      +--------------------------------------------------------+
      | +--------------+                                       |
      | |[VIT.] [CAP]  | tableau de bord                       |
      | | lat    lon   |                                       |
      | +--------------+                                  [+]  |
      |                               +----------------+  [-]  |
      |                               | camera (ouv.)  |  [cam]|
      |                               +----------------+  [rec]|
      |                                                        |
      | +------------+   +---------------------------+         |
      | |[couche  v] |   | outil      outil en cours |         |
      | | legende    |   | [cellule][cellule] -->    |         |
      | +------------+   +---------------------------+         |
      |                                           attribution  |
      +--------------------------------------------------------+

   Bas de carte (.dash-bas) en grille de trois colonnes, les deux laterales a
   la largeur de la legende : la boite Outils est centree sur la carte et ne
   peut pas atteindre la legende. Tout le bas se pose 20 px au-dessus du
   bord, au-dessus de l'attribution des tuiles.
   La box camera se place a gauche de la colonne de controles, centree sur
   la carte quand la hauteur le permet, sinon remontee au-dessus des boites
   du bas : bornes en variables (--dash-cam-haut, --dash-cam-bas), sans
   script.

   Les classes lues par js/app.js (sf-legend-*, live-cell*, open/mini,
   active, show, body.cam-active) sont conservees ; elles sont prefixees
   ici par un conteneur de la page, car app.css est aussi lu par
   parametres.html, dont l'apercu de legende emploie les memes classes
   .sf-legend-* (section 5).
   La carte, le zoom, les controles B9 et l'attribution (regles en .dash)
   servent aussi a modulation.html (<main class="dash mod">, section 4) et
   champs.html (section 6) ; la colonne de droite est propre au tableau de
   bord (.dash-controls--droite).
   ========================================================================== */
.app-shell > main.dash {
    overflow: hidden;
    --dash-marge: 16px;
    /* Modulation et Champs : bloc de controles sous le zoom Leaflet en ligne
       (marge 16 + 44 + 8 px d'air) */
    --dash-controles-top: 68px;
    /* Colonne de droite du tableau de bord : demi-hauteur de la place
       reservee a Recentrer, pour centrer le couple visible zoom + camera */
    --dash-controles-decalage: calc((var(--tap) + 8px) / 2);
    /* Bas des boites : au-dessus de l'attribution (14 px) avec 6 px d'air */
    --dash-bas: 20px;
    --dash-legende-w: 296px;
    /* Hauteurs au plus des boites, mesurees : Tableau de bord (tuiles
       vitesse et cap), legende (selecteur et echelle), Outils (tete, une ligne
       de cellules a champ Cible et barre de defilement). Elles bornent la
       camera sans script. */
    --dash-bord-h: 72px;
    --dash-legende-h: 138px;
    --dash-outils-h: var(--dash-legende-h);
    /* Legende reduite a son selecteur (echelle repliee, carte basse) :
       padding + cible de 44 px */
    --dash-legende-min-h: 68px;
    /* Box camera : tete 60 px + filet + bordures (63 px), corps en 16/9.
       Sa largeur suit la hauteur libre entre les bornes haute et basse. */
    --dash-cam-haut: var(--dash-marge);
    --dash-cam-bas: calc(var(--dash-bas) + var(--dash-outils-h) + 8px);
    --dash-mini-bas: var(--dash-cam-bas);
    --dash-cam-w-max: 360px;
    --dash-cam-w: clamp(236px,
        (100dvh - var(--topbar-h) - var(--dash-cam-haut) - var(--dash-cam-bas) - 63px) * 16 / 9 + 2px,
        var(--dash-cam-w-max));
    --dash-cam-h: calc(63px + (var(--dash-cam-w) - 2px) * 9 / 16);
    --dash-mini-w: 184px;
    --dash-mini-h: calc(2px + (var(--dash-mini-w) - 2px) * 9 / 16);
}

/* z-index 0 : isole ceux de Leaflet (panneaux 400, controles 1000), les
   boites glass qui suivent n'ont qu'a valoir 1 */
.dash__map {
    position: absolute;
    inset: 0;
    z-index: 0;
    background: var(--bg-3);
}

/* Liste du select, champs et barres de defilement sombres dans le glass */
.dash .glass,
.dash-bord,
.dash-live { color-scheme: dark; }

/* ---------- Controles de carte (B9) ----------
   Modulation et Champs : bloc compact de 2 colonnes (variante --row de la
   charte) en haut a gauche, zoom puis fond de carte. Tableau de bord :
   colonne au centre droit (.dash-controls--droite, plus bas). */
.dash .leaflet-top.leaflet-left .leaflet-control {
    margin: var(--dash-marge) 0 0 var(--dash-marge);
}
.dash .leaflet-bar {
    display: flex;
    flex-direction: row;
    gap: 4px;
    border: none;
    border-radius: 0;
    box-shadow: none;
    background: transparent;
}
/* Zoom Leaflet dessine comme .map-btn (components.css). leaflet.css est lu
   avant app.css : a specificite egale, ces regles gagnent. */
.dash .leaflet-bar a {
    width: var(--tap);
    height: var(--tap);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--glass-soft);
    border: 1px solid var(--glass-line);
    color: var(--glass-ink);
    font: 700 20px/1 var(--display);
    text-indent: 0;
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
}
/* Leaflet arrondit seulement les coins exterieurs de la barre, par
   .leaflet-touch .leaflet-bar a:first-child / :last-child (0,3,1), pose sur
   tout navigateur a pointer events, bureau compris : meme specificite ici. */
.dash .leaflet-bar a:first-child,
.dash .leaflet-bar a:last-child { border-radius: var(--r-md); }
.dash .leaflet-bar a:hover,
.dash .leaflet-bar a:focus { background: var(--glass); color: var(--glass-ink); }
.dash .leaflet-bar a:active { background: var(--brand-fill); border-color: var(--brand-fill); }
.dash .leaflet-bar a.leaflet-disabled {
    background: var(--glass-soft);
    color: var(--glass-muted);
}
/* Attribution des tuiles : seul bloc de Leaflet pose a plat sur la carte,
   passe en glass comme les panneaux voisins (blanc et Helvetica sinon) */
.dash .leaflet-control-attribution {
    padding: 0 8px;
    background: var(--glass-soft);
    border-top-left-radius: var(--r-sm);
    color: var(--glass-muted);
    font-family: var(--mono);
    font-size: 10px;
}
.dash .leaflet-control-attribution a { color: var(--glass-ink-soft); }
/* #map est focalisable (fleches du clavier) et remplit main.dash, qui coupe
   ce qui depasse. Un outline rentre ne suffit pas : il est peint sous les
   panneaux de Leaflet (z-index 400 a 1000). L'anneau est un calque pose
   au-dessus d'eux, a l'interieur de la carte. */
.dash__map:focus-visible { outline: none; }
.dash__map:focus-visible::after {
    content: "";
    position: absolute;
    inset: 2px;
    z-index: 1001;
    border: 2px solid var(--brand);
    pointer-events: none;
}
/* Leaflet focalise la carte par script au clic : Chrome garde :focus-visible
   si le focus sortait d'un champ texte. is-pointeur (pose au pointerdown,
   retire a la premiere touche par app.js et modulation.js) masque l'anneau. */
.dash__map.is-pointeur:focus-visible::after { display: none; }

.dash-controls {
    position: absolute;
    top: var(--dash-controles-top);
    left: var(--dash-marge);
    z-index: 1;
    width: calc(var(--tap) * 2 + 4px);
    display: flex;
    flex-wrap: wrap;
    gap: 8px 4px;
}
.dash-controls__group {
    display: flex;
    gap: 4px;
}
.dash-controls .map-btn:hover { background: var(--glass); }
/* Fond de carte courant (.active, pages Champs et Modulation) et camera
   affichee : selection = marque, en --brand-fill comme la tuile Vitesse et
   l'entree active voisines (icone blanche a 4,6:1 au lieu de 3,6:1 sur
   --brand) */
.dash-controls .map-btn.active,
.dash-controls .map-btn[aria-pressed="true"] {
    background: var(--brand-fill);
    border-color: var(--brand-fill);
    color: var(--on-brand);
}
.dash-controls .map-btn.active:hover,
.dash-controls .map-btn[aria-pressed="true"]:hover {
    background: var(--brand-fill-deep);
    border-color: var(--brand-fill-deep);
}

/* Colonne du tableau de bord, contre le bord droit de la carte (donc a
   gauche de la sidebar, retractee ou non) : zoom Leaflet (deplace dans
   #dashZoom par initMap), camera, recentrer.
   Ce qui est centre sur la hauteur de la carte, c'est le couple VISIBLE
   zoom + camera : la colonne est descendue de la demi-hauteur de la place
   reservee a Recentrer (44 + 8 px), sinon le couple restait 26 px trop haut
   tant que le suivi de position etait actif, c'est-a-dire presque toujours. */
.dash-controls--droite {
    top: 50%;
    left: auto;
    right: var(--dash-marge);
    width: var(--tap);
    flex-direction: column;
    flex-wrap: nowrap;
    gap: 8px;
    transform: translateY(calc(-50% + var(--dash-controles-decalage)));
}
/* Carte de moins de 288 px : descendre la colonne poserait Recentrer sur
   l'attribution des tuiles. Elle reprend alors le centrage de la colonne
   entiere, place reservee comprise. */
@media (max-height: 350px) {
    .app-shell > main.dash { --dash-controles-decalage: 0px; }
}
/* Place du zoom reservee avant que Leaflet ne l'insere : la colonne ne
   saute pas au chargement */
.dash-controls__zoom { min-height: calc(var(--tap) * 2 + 4px); }
.dash-controls--droite .leaflet-bar { flex-direction: column; }

/* Recentrer : absent tant que la carte suit la position. Il apparait en
   couleur d'action : c'est le seul signe que le suivi est coupe. Sa place
   reste reservee (visibility, qui le retire aussi du parcours clavier) :
   zoom et camera ne bougent pas sous le doigt quand il apparait. */
.dash-recenter { visibility: hidden; }
.dash-controls .dash-recenter.show {
    visibility: visible;
    background: var(--brand-fill);
    border-color: var(--brand-fill);
    color: var(--on-brand);
}
.dash-controls .dash-recenter.show:hover {
    background: var(--brand-fill-deep);
    border-color: var(--brand-fill-deep);
}

/* ---------- Marqueur GPS (divIcon .gps-marker, app.js) ---------- */
.dash .gps-marker {
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
}
.dash .gps-marker-dot {
    width: 22px;
    height: 22px;
    background: var(--brand-bright);
    border: 3px solid #FFFFFF;
    border-radius: 50%;
    /* Halo --brand-bright translucide : visible sur l'imagerie claire ou sombre */
    box-shadow: 0 0 0 8px rgba(33, 150, 255, 0.25);
    animation: sf-gps-pulse 2s ease-in-out infinite;
}
@keyframes sf-gps-pulse {
    0%, 100% { box-shadow: 0 0 0 8px rgba(33, 150, 255, 0.25); }
    50%      { box-shadow: 0 0 0 14px rgba(33, 150, 255, 0.10); }
}

/* ---------- Boite Tableau de bord (C7, haut-gauche) ----------
   Largeur fixe : la vitesse qui passe de 9,5 a 10,5 ne fait pas bouger la
   boite. */
.dash-bord {
    position: absolute;
    top: var(--dash-marge);
    left: var(--dash-marge);
    z-index: 1;
    width: 264px;
    padding: 12px;
    gap: 8px;
}
/* Ecart de C7 (10 px) ramene aux multiples de 4 */
.dash-bord__stats { grid-template-columns: 1fr; gap: 8px; }
/* Unite sur la tuile --brand-fill : le gris du glass y tombe sous 3:1, le
   blanc a 85 % sous 4,5:1 */
.dash-bord .dash-stat--brand .dash-stat__value small { color: var(--on-brand); }

/* ---------- Bas de carte : legende et outils ----------
   Le conteneur couvre toute la largeur : il laisse passer les gestes de
   carte, seules ses deux boites les captent. */
.dash-bas {
    position: absolute;
    left: var(--dash-marge);
    right: var(--dash-marge);
    bottom: var(--dash-bas);
    z-index: 1;
    display: grid;
    grid-template-columns: var(--dash-legende-w) minmax(0, 1fr) var(--dash-legende-w);
    column-gap: 8px;
    align-items: end;
    pointer-events: none;
}
.dash-bas > * { pointer-events: auto; }

/* ---------- Legende et selecteur de couche (glass, bas-gauche) ----------
   Une boite : la legende decrit la couche choisie juste au-dessus. */
.dash-legend {
    width: var(--dash-legende-w);
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 12px;
}
.dash-layer {
    display: flex;
    align-items: center;
    gap: 12px;
}
.dash-layer__label {
    flex-shrink: 0;
    font-family: var(--mono);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--glass-muted);
}
.dash-layer__field {
    position: relative;
    display: flex;
    flex: 1 1 auto;
    min-width: 0;
}
.dash-layer__select {
    width: 100%;
    min-width: 0;
    height: var(--tap);
    padding: 0 40px 0 12px;
    -webkit-appearance: none;
    appearance: none;
    background: var(--glass-fill);
    border: 1px solid var(--glass-rule);
    border-radius: var(--r-md);
    color: var(--glass-ink);
    font-family: var(--display);
    font-size: 14px;
    font-weight: 700;
    text-overflow: ellipsis;
    cursor: pointer;
}
.dash-layer__select:hover { border-color: var(--glass-muted); }
/* Firefox peint la liste deroulante avec le fond du select, translucide ici */
.dash-layer__select option {
    background: var(--glass-solid);
    color: var(--glass-ink);
}
.dash-layer__chev {
    position: absolute;
    top: 50%;
    right: 12px;
    width: 16px;
    height: 16px;
    transform: translateY(-50%);
    color: var(--glass-muted);
    pointer-events: none;
}
/* Echelle : information purement visuelle (aria-hidden dans index.html) */
.dash-legend__echelle { user-select: none; }
.dash-legend .sf-legend-title {
    margin-bottom: 8px;
    font-family: var(--mono);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--glass-muted);
}
/* 9 bandes nettes aux seuils par defaut (10/20/40/60 %), reecrites par
   buildLegend des que la configuration est lue. Couleurs metier en dur :
   elles portent l'ecart a la cible, pas la marque (22-CARTE-LIVE.md). */
.dash-legend .sf-legend-bar {
    height: 12px;
    border: 1px solid var(--glass-rule);
    border-radius: var(--r-sm);
    background: linear-gradient(to right,
        #e10600  0%, #e10600 20%,
        #ff9b00 20%, #ff9b00 30%,
        #ffbe00 30%, #ffbe00 40%,
        #ffed00 40%, #ffed00 45%,
        #209f00 45%, #209f00 55%,
        #00daff 55%, #00daff 60%,
        #0035ff 60%, #0035ff 70%,
        #6400ff 70%, #6400ff 80%,
        #f100ff 80%, #f100ff 100%);
}
/* Graduations posees sous leur borne : left en % ecrit par buildLegend,
   centrees, sauf les bornes d'axe alignees vers l'interieur. Les classes
   is-gauche / is-droite ecartent la tolerance de part et d'autre de la
   bande verte, is-masquee cache une paire qui en chevaucherait une autre. */
.dash-legend .sf-legend-scale {
    position: relative;
    height: 16px;
    margin-top: 4px;
    font-family: var(--mono);
    font-size: 10px;
    font-weight: 500;
    line-height: 16px;
    font-variant-numeric: tabular-nums;
    color: var(--glass-ink-soft);
}
.dash-legend .sf-legend-scale > span {
    position: absolute;
    top: 0;
    white-space: nowrap;
    transform: translateX(-50%);
}
.dash-legend .sf-legend-scale > :first-child,
.dash-legend .sf-legend-scale > .is-droite { transform: none; }
.dash-legend .sf-legend-scale > :last-child,
.dash-legend .sf-legend-scale > .is-gauche { transform: translateX(-100%); }
.dash-legend .sf-legend-scale > .is-masquee { visibility: hidden; }

/* ---------- Box camera (glass, a gauche de la colonne de controles) ----------
   Fermee : invisible (visibility retire ses boutons du parcours clavier),
   flux coupe par app.js ; elle apparait en fondu depuis la colonne.
   Ouverte : pleine taille. Reduite (.mini) : vignette sans tete qui continue
   de jouer ; un clic dessus la rouvre. Le <video> garde une taille reelle
   dans les trois etats (iOS fige une video masquee).
   Hauteur : centree sur la carte, remontee si elle atteindrait la borne
   basse (--dash-cam-bas, boites du bas), jamais au-dessus de la borne
   haute (--dash-cam-haut). Quand la hauteur libre entre les deux bornes est
   plus courte que la box, c'est la BOX qui cede (--dash-cam-h-eff, reprise
   en max-height) : sinon la borne haute l'emportait et la box passait par
   dessus les Outils, c'est-a-dire par dessus les valeurs live, justement
   pendant le travail. La video reste entiere (object-fit: contain), avec des
   bandes noires. */
.cam-panel {
    position: absolute;
    --dash-cam-h-eff: min(var(--dash-cam-h),
        100% - var(--dash-cam-haut) - var(--dash-cam-bas));
    top: max(var(--dash-cam-haut),
        min(50% - var(--dash-cam-h-eff) / 2, 100% - var(--dash-cam-bas) - var(--dash-cam-h-eff)));
    max-height: var(--dash-cam-h-eff);
    right: calc(var(--dash-marge) + var(--tap) + 8px);
    z-index: 2;
    width: var(--dash-cam-w);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    opacity: 0;
    transform: translateX(16px);
    visibility: hidden;
    pointer-events: none;
    transition: opacity 0.22s ease, transform 0.22s ease, visibility 0s linear 0.22s;
}
.cam-panel.open,
.cam-panel.mini {
    opacity: 1;
    transform: none;
    visibility: visible;
    pointer-events: auto;
    transition: opacity 0.22s ease, transform 0.22s ease;
}
.cam-panel.mini {
    --dash-cam-h-eff: min(var(--dash-mini-h),
        100% - var(--dash-cam-haut) - var(--dash-mini-bas));
    top: max(var(--dash-cam-haut),
        min(50% - var(--dash-cam-h-eff) / 2, 100% - var(--dash-mini-bas) - var(--dash-cam-h-eff)));
    width: var(--dash-mini-w);
    cursor: pointer;
}
.cam-panel.mini .cam-panel__head { display: none; }
.cam-panel__head {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 8px 8px 8px 16px;
    border-bottom: 1px solid var(--glass-rule);
}
/* Titre compressible : sous 234 px de large (petit telephone), il se tronque
   plutot que de pousser le bouton Fermer hors du panneau */
.cam-panel__title {
    flex: 1 1 0;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-family: var(--display);
    font-size: 16px;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--glass-ink);
}
.cam-panel__btns {
    display: flex;
    gap: 4px;
}
/* min-height: 0 : sans lui, la taille minimale automatique d'un element
   flex garderait le 16/9 et la max-height de la box rognerait le bas de
   l'image au lieu de la laisser se reduire */
.cam-panel__body {
    position: relative;
    min-height: 0;
    aspect-ratio: 16 / 9;
    background: #000;
}
.cam-panel__body video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    display: block;
    object-fit: contain;
    background: #000;
}
/* Message d'etat (sans URL, connexion, flux indisponible) : app.js bascule
   son display entre flex et none */
.cam-empty {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
    text-align: center;
    font-size: 14px;
    line-height: 1.5;
    color: var(--glass-ink-soft);
}
.cam-panel.mini .cam-empty {
    padding: 8px;
    font-size: 12px;
    line-height: 1.4;
}
/* Indice "agrandir" de la vignette, non cliquable : le clic va au panneau */
.cam-expand-hint {
    position: absolute;
    top: 8px;
    right: 8px;
    width: 28px;
    height: 28px;
    display: none;
    align-items: center;
    justify-content: center;
    background: var(--glass-soft);
    border-radius: var(--r-sm);
    color: var(--glass-ink);
    pointer-events: none;
}
.cam-expand-hint .icon { width: 16px; height: 16px; }
.cam-panel.mini .cam-expand-hint { display: inline-flex; }

/* ---------- Boite Outils en cours (C7, bas-centre) ----------
   Largeur du contenu, bornee par la colonne centrale de .dash-bas
   (max-width) : au-dela, les cellules defilent a l'horizontale. */
.dash-live {
    justify-self: center;
    width: auto;
    min-width: 0;
    max-width: 100%;
    height: var(--dash-outils-h);
    padding: 12px;
    gap: 8px;
}
.dash-live__head {
    display: flex;
    align-items: baseline;
    gap: 8px;
    min-width: 0;
}
.dash-live__name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-family: var(--display);
    font-size: 16px;
    font-weight: 700;
    letter-spacing: -0.01em;
    line-height: 20px;
    color: var(--glass-ink);
}
/* Le libelle fixe cede avant le nom de l'outil : incompressible, c'etait le
   NOM qui disparaissait sur une boite etroite (camera ouverte a
   l'horizontale), et il ne restait que « Outil en cours ». */
.dash-live__head small {
    flex-shrink: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-family: var(--mono);
    font-size: 10px;
    font-weight: 500;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--glass-muted);
}

/* Cellules sur une ligne qui defile. Marge negative + padding : l'anneau de
   focus des champs Cible n'est pas rogne par le defilement. */
.dash-live__cells {
    display: flex;
    flex: 1 1 auto;
    min-height: 0;
    align-items: stretch;
    gap: 8px;
    margin: -4px;
    padding: 4px;
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
}
.dash-live .live-row-empty {
    white-space: nowrap;
    font-size: 14px;
    color: var(--glass-muted);
}
.dash-live .live-cell {
    flex: 0 0 104px;
    min-width: 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 2px;
    padding: 6px 8px;
    background: var(--glass-fill);
    border: 1px solid var(--glass-rule);
    border-radius: var(--r-md);
    cursor: pointer;
    scroll-snap-align: start;
    transition: background 0.12s ease, border-color 0.12s ease;
}
.dash-live .live-cell > * { flex-shrink: 0; }
.dash-live .live-cell:hover { border-color: var(--glass-muted); }
/* Couche peinte sur la carte : selection = marque */
.dash-live .live-cell.layer-active {
    background: var(--glass-brand);
    border-color: var(--brand);
}
.dash-live .live-cell-label,
.dash-live .live-cell-target-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-family: var(--mono);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--glass-muted);
}
.dash-live .live-cell-target-label { flex-shrink: 0; }
.dash-live .live-cell-value,
.dash-live .live-cell-target-value {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-family: var(--mono);
    font-size: 13px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    line-height: 1.2;
    color: var(--glass-ink);
}
/* Valeur nulle : texte attenue plutot que cellule transparente, qui
   tomberait sous le contraste lisible sur le glass */
.dash-live .live-cell.zero .live-cell-value { color: var(--glass-muted); }
.dash-live .live-cell-unit {
    margin-left: 4px;
    font-family: var(--sans);
    font-size: 11px;
    font-weight: 500;
    color: var(--glass-muted);
}
/* Cible sur une ligne : la cellule reste basse */
.dash-live .live-cell-target {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 1px;
    padding-top: 2px;
}
/* Champ B3 en version glass : bordure 1,5 px, rayon et anneau de focus de
   .field__input-wrap */
.dash-live .live-cell-target-input {
    width: 100%;
    min-width: 0;
    height: 34px;
    padding: 0 8px;
    background: var(--glass-soft);
    border: 1.5px solid var(--glass-muted);
    border-radius: var(--r-md);
    color: var(--glass-ink);
    /* 16 px comme B3 : en dessous, Safari iOS zoome la page au focus */
    font-size: 16px;
    font-weight: 600;
    -moz-appearance: textfield;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.dash-live .live-cell-target-input::-webkit-outer-spin-button,
.dash-live .live-cell-target-input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}
.dash-live .live-cell-target-input:focus {
    border-color: var(--brand);
    box-shadow: 0 0 0 4px var(--glass-brand);
}
.dash-live .live-cell-target-input:focus-visible { outline: none; }

/* ---------- Carte etroite : bas en pile ----------
   Sous 1066 px de carte, la colonne centrale laisserait moins de trois
   cellules (426 px avec le padding). Le bas passe en pile : legende
   au-dessus, Outils sur toute la largeur jusqu'a la colonne de controles,
   qui reste libre a toute hauteur d'ecran. Requete sur la largeur de la
   carte et non de l'ecran : la sidebar deployee la reduit de 128 px (des
   1025 px d'ecran : 833 px de carte). La legende ne descend pas sous la
   camera : a ces largeurs, elles ne se croisent pas (voir <= 700 px). */
[data-page="tableau-de-bord"] > main.dash { container: dash / inline-size; }
@container dash (max-width: 1065px) {
    .dash-bas {
        right: calc(var(--dash-marge) + var(--tap) + 8px);
        display: flex;
        flex-direction: column;
        align-items: flex-start;
        row-gap: 8px;
    }
    .dash-bas > .dash-live { align-self: stretch; }
}

/* ---------- Tablette et mobile (<= 1024 px, meme seuil que le tiroir) ----
   Pile du bas ci-dessus ; Tableau de bord et legende compacts, a la meme
   largeur. */
@media (max-width: 1024px) {
    .app-shell > main.dash {
        --dash-legende-w: 232px;
        --dash-bord-h: 60px;
        --dash-legende-h: 122px;
        --dash-legende-min-h: 60px;
        --dash-outils-h: 180px;
    }
    .dash-bord {
        width: var(--dash-legende-w);
        padding: 8px;
    }
    .dash-bord .dash-stat { padding: 8px 12px; }
    .dash-bord .dash-stat__value { font-size: 18px; }

    .dash-legend {
        gap: 8px;
        padding: 8px 12px;
    }
    .dash-legend .sf-legend-title { margin-bottom: 4px; }
}

/* Attribution sur deux lignes sous 500 px de large (texte de 484 px) : le
   bas des boites remonte d'autant */
@media (max-width: 500px) {
    .app-shell > main.dash { --dash-bas: 34px; }
}

/* ---------- Carte basse (fenetre de 560 px de haut au plus) ----------
   La pile du bas prend --dash-bas + Outils + 8 + legende, le Tableau de bord
   16 + sa hauteur : il faut pres de 500 px de carte. Sous cette hauteur, la
   legende montait par dessus le Tableau de bord et masquait la ligne
   LAT / LON, sans aucun signe d'erreur (1100 x 500, 800 x 484, 400 x 500).
   L'echelle de couleurs se replie donc : c'est la seule information de la
   page qui ne change jamais (aria-hidden, elle ne dit rien au clavier ni au
   lecteur d'ecran), et le selecteur de couche, lui, est un controle : il
   reste. La boite tombe a --dash-legende-min-h.
   Le repli vaut aussi pour la grille (carte large et fenetre basse), ou la
   legende croisait le Tableau de bord sous 362 px de carte. */
@media (max-height: 560px) {
    .app-shell > main.dash { --dash-legende-h: var(--dash-legende-min-h); }
    .dash-legend__echelle { display: none; }

    /* Carte d'au moins 700 px : meme repliee, la pile ne tient pas sous
       490 px de carte (1100 x 484). Le bas passe sur une ligne, comme le
       telephone a l'horizontale plus bas, tant que la carte est assez large
       pour porter la legende ET les Outils cote a cote. Plus etroite
       (telephone), la pile repliee suffit jusqu'a 460 px de fenetre. */
    @container dash (min-width: 700px) and (max-width: 1065px) {
        .dash-bas {
            flex-direction: row;
            align-items: flex-end;
            column-gap: 8px;
        }
        .dash-bas > .dash-legend { flex-shrink: 0; }
        .dash-bas > .dash-live {
            flex: 1 1 0;
            align-self: auto;
        }
    }
}

/* Telephone : la camera deborderait sur le Tableau de bord (haut-gauche) et
   la legende (au-dessus des Outils) : elle se loge entre les deux, sans
   depasser la largeur a gauche de la colonne de controles. */
@media (max-width: 700px) {
    .app-shell > main.dash {
        --dash-cam-haut: calc(var(--dash-marge) + var(--dash-bord-h) + 8px);
        --dash-cam-bas: calc(var(--dash-bas) + var(--dash-outils-h) + 8px + var(--dash-legende-h) + 8px);
        --dash-cam-w-max: min(360px, 100vw - 84px);
    }
}
/* Telephone court : la camera ouverte (236 px de large au moins, largeur de
   sa tete) ne tient plus entre le Tableau de bord et la legende entiere.
   C'est l'echelle qui se replie le temps que la box est ouverte en grand, et
   non la boite : effacee (visibility), elle emportait le selecteur de couche
   avec elle, hors du parcours clavier, et il n'y avait plus aucun moyen de
   changer la couche peinte sans fermer la camera (les cellules live sont des
   <div> sans tabindex). La box descend donc jusqu'a la legende repliee et
   perd un peu de hauteur ; la vignette, elle, tient au-dessus de la legende
   entiere. */
@media (max-width: 700px) and (max-height: 724px) {
    .app-shell > main.dash {
        --dash-cam-bas: calc(var(--dash-bas) + var(--dash-outils-h) + 8px + var(--dash-legende-min-h) + 8px);
        --dash-mini-bas: calc(var(--dash-bas) + var(--dash-outils-h) + 8px + var(--dash-legende-h) + 8px);
    }
    body.cam-active .cam-panel.open ~ .dash-bas .dash-legend__echelle { display: none; }
}

/* Telephone a l'horizontale (hauteur <= 480 px) : la carte fait au plus
   416 px de haut, la pile legende + Outils (plus de 300 px) ne tient pas.

      +---------------------------------------------------+
      | [VIT.][CAP]                                 [+]   |
      | lat  lon                  +-----------+     [-]   |
      |                           | camera    |     [cam] |
      | [couche  v]  +----------+ +-----------+     [rec] |
      |              | outils ->|                         |
      +---------------------------------------------------+

   Colonne gauche de la largeur de la legende (Tableau de bord en haut,
   legende repliee a son selecteur en bas, cf. la regle en hauteur ci-dessus),
   Outils entre elle et les controles. La camera affichee reduit la boite
   Outils par la droite plutot que de la couvrir. */
@media (max-width: 1024px) and (max-height: 480px) {
    .app-shell > main.dash {
        --dash-cam-haut: var(--dash-marge);
        --dash-cam-bas: var(--dash-bas);
        --dash-mini-bas: var(--dash-bas);
        --dash-cam-w: 236px;
    }
    .dash-bas {
        flex-direction: row;
        align-items: flex-end;
        column-gap: 8px;
    }
    .dash-bas > .dash-legend { flex-shrink: 0; }
    .dash-bas > .dash-live {
        flex: 1 1 0;
        align-self: auto;
    }
    body.cam-active .cam-panel.open ~ .dash-bas > .dash-live { margin-right: calc(var(--dash-cam-w) + 8px); }
    body.cam-active .cam-panel.mini ~ .dash-bas > .dash-live { margin-right: calc(var(--dash-mini-w) + 8px); }
    /* Boite Outils reduite a une centaine de pixels (667 x 375, camera
       ouverte) : le libelle fixe laisse la place au nom de l'outil, seule
       information variable de la tete */
    body.cam-active .cam-panel.open ~ .dash-bas .dash-live__head small { display: none; }
}

@media (max-width: 480px), (max-width: 1024px) and (max-height: 480px) {
    /* Libelle reserve aux lecteurs d'ecran : le select nomme deja la couche */
    .dash-layer__label {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
    }
}

@media (prefers-reduced-motion: reduce) {
    .dash .gps-marker-dot { animation: none; }
    .cam-panel,
    .cam-panel.open,
    .cam-panel.mini { transition: none; }
}

/* ==========================================================================
   4. MODULATION (modulation.html)
   --------------------------------------------------------------------------
   Meme carte que le tableau de bord : <main class="dash mod"> reprend de la
   section 3 la carte plein cadre (.dash__map), le zoom Leaflet et le fond de
   carte en controles B9 (.dash-controls), l'attribution en glass et l'anneau
   de focus de la carte. S'y ajoute le panneau C7 de dessin.

      Desktop (> 1024 px)                 Tablette (601 a 1024 px)
      +--------------------------------+  +--------------------------------+
      | [+][-]          +------------+ |  | [+][-]                         |
      | [sat][plan]     | titre      | |  | [sat][plan]                    |
      |                 | nom  unite | |  |                                |
      |                 | [+ zone]   | |  | +----------------------------+ |
      |                 | message    | |  | | titre      | zones         | |
      |                 | zones      | |  | | nom  unite | [SHP][KML]    | |
      |                 | [SHP][KML] | |  | | [+ zone]   |               | |
      |                 +------------+ |  | | message    |               | |
      |                                |  | +----------------------------+ |
      +--------------------------------+  +--------------------------------+
                            320 px          hauteur 50 % au plus

   Telephone (<= 600 px) : panneau bas sur une colonne. Telephone a
   l'horizontale (hauteur <= 480 px) : panneau a droite, car la moitie d'une
   carte de 300 px de haut ne laisserait voir que le titre. Le panneau defile
   en entier, jamais une liste dans la liste : un defilement imbrique est
   penible au doigt.

   Classes lues par js/modulation.js : .mod-zone (et .selected), .mod-swatch,
   .mod-zone-unit, .mod-zone-del (lignes rendues par buildLegendRows),
   .mod-poly (et .is-selected), .mod-draft, .mod-vertex, .mod-mid, .mod-snap,
   .mod-tip (calques Leaflet), .mod-drawing (carte en cours de dessin). L'attribut hidden de #modDraw,
   #modDraftActions et #modZonesHead est pose par le script.
   ========================================================================== */

/* ---------- Panneau (C7) ---------- */
.mod-panel {
    position: absolute;
    top: var(--dash-marge);
    right: var(--dash-marge);
    z-index: 1;
    width: 320px;
    /* Laisse lisible l'attribution des tuiles, en bas a droite */
    max-height: calc(100% - 52px);
    /* Ecarts de C7 (18 et 16 px) ramenes a ceux du panneau live */
    padding: 16px;
    gap: 12px;
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: thin;
    /* Champs, curseur de saisie et barre de defilement sombres sur le glass */
    color-scheme: dark;
}
.mod-panel__edit,
.mod-panel__list {
    flex-shrink: 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 12px;
}
.mod-panel .dash-panel__title small { margin-top: 4px; }

/* ---------- Nom et unite : champs B3 transposes sur glass ---------- */
.mod-fields {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 96px;
    gap: 8px;
}
.mod-field {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.mod-field__label {
    font-family: var(--mono);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--glass-muted);
}
/* Meme dessin que le champ Cible du panneau live (.live-cell-target-input) */
.mod-input {
    width: 100%;
    min-width: 0;
    height: var(--tap);
    padding: 0 12px;
    background: var(--glass-soft);
    border: 1.5px solid var(--glass-muted);
    border-radius: var(--r-md);
    color: var(--glass-ink);
    /* 16 px comme B3 : en dessous, Safari iOS zoome la page au focus */
    font-size: 16px;
    font-weight: 500;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.mod-input:focus {
    border-color: var(--brand);
    box-shadow: 0 0 0 4px var(--glass-brand);
}
.mod-input:focus-visible { outline: none; }

/* ---------- Boutons ---------- */
.mod-draft-actions,
.mod-export {
    display: flex;
    gap: 8px;
}
.mod-draft-actions .btn,
.mod-export .btn {
    flex: 1 1 0;
    min-width: 0;
    padding: 0 12px;
}
/* Ghost de la charte (fond --bg) transpose sur glass : en theme clair, il
   serait sinon la seule tache blanche du panneau. Bordure au contraste de
   --line-strong sur son fond (environ 1,9:1) : a 55 % de blanc, le bouton
   secondaire se lirait comme une action principale. */
.mod-panel .btn--ghost {
    background: transparent;
    border-color: var(--glass-line-strong);
    color: var(--glass-ink);
}
.mod-panel .btn--ghost:hover {
    background: var(--glass-fill);
    border-color: var(--glass-muted);
}
/* Sous 360 px, deux boutons cote a cote couperaient "Export SHP" en deux lignes */
@media (max-width: 360px) {
    .mod-export { flex-direction: column; }
    /* flex-basis 0 s'appliquerait a la hauteur : boutons ecrases sous 52 px */
    .mod-export .btn { flex: none; }
}

/* ---------- Zones : une ligne de 44 px par zone ----------
   La ligne est le champ (B3) : pastille, dose, unite et bouton supprimer a
   l'interieur, anneau de focus sur la ligne. Le champ remplit la hauteur :
   cible tactile de 44 px sans ligne plus haute. Bordure en ombre interne,
   qui n'ajoute pas 2 px a la hauteur. */
.mod-zones-head {
    font-family: var(--mono);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--glass-muted);
}
.mod-zones {
    display: flex;
    flex-direction: column;
    gap: 4px;
}
/* Sans zone, pas d'ecart de 12 px autour d'une liste vide */
.mod-zones:empty { display: none; }
.mod-zone {
    height: var(--tap);
    display: flex;
    align-items: center;
    gap: 8px;
    padding-left: 12px;
    background: var(--glass-fill);
    border-radius: var(--r-md);
    box-shadow: inset 0 0 0 1px var(--glass-rule);
    cursor: pointer;
    transition: background 0.12s ease, box-shadow 0.15s ease;
}
.mod-zone:hover { box-shadow: inset 0 0 0 1px var(--glass-muted); }
/* Zone selectionnee (poignees affichees sur la carte) : selection = marque */
.mod-zone.selected {
    background: var(--glass-brand);
    box-shadow: inset 0 0 0 1px var(--brand);
}
.mod-zone:focus-within {
    box-shadow: inset 0 0 0 1.5px var(--brand), 0 0 0 4px var(--glass-brand);
}
.mod-swatch {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    border-radius: var(--r-sm);
    box-shadow: inset 0 0 0 1px var(--glass-rule);
}
.mod-zone input {
    flex: 1 1 auto;
    width: 0;
    min-width: 0;
    height: 100%;
    padding: 0 4px;
    background: transparent;
    border: none;
    color: var(--glass-ink);
    font-family: var(--mono);
    font-size: 16px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    text-align: right;
}
.mod-zone input::placeholder {
    color: var(--glass-muted);
    font-family: var(--sans);
    font-weight: 500;
}
/* L'anneau de la ligne (:focus-within) signale deja le focus */
.mod-zone input:focus-visible { outline: none; }
.mod-zone-unit {
    flex-shrink: 0;
    max-width: 72px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 12px;
    font-weight: 500;
    color: var(--glass-muted);
}
.mod-zone-del {
    flex-shrink: 0;
    width: var(--tap);
    height: var(--tap);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    border-radius: var(--r-md);
    color: var(--glass-muted);
    cursor: pointer;
    transition: background 0.12s ease, color 0.12s ease;
}
.mod-zone-del .icon { width: 18px; height: 18px; }
.mod-zone-del:hover {
    background: var(--danger-soft);
    color: var(--danger);
}
/* Contour a l'interieur : a l'exterieur il chevaucherait l'anneau de la ligne */
.mod-zone-del:focus-visible { outline-offset: -2px; }

/* ---------- Message (role=status) ----------
   Dans le bloc de dessin, sous Terminer : la liste peut etre longue, un
   message en fin de panneau s'ecrirait hors de vue. */
.mod-msg {
    /* msg() le fait defiler dans le panneau : pas colle au bord */
    scroll-margin: 16px;
    font-size: 13px;
    line-height: 1.4;
    color: var(--glass-ink-soft);
}
/* Vide, il reste dans la page (region live annoncee a la prochaine
   ecriture) mais rend l'ecart qui le precede */
.mod-msg:empty { margin-top: -12px; }

/* ---------- Calques Leaflet (dessin et edition) ----------
   Couleurs fixes, independantes du theme : elles se posent sur l'image
   satellite, comme le glass. Contour de zone blanc, --brand pour la zone
   selectionnee (comme sa ligne), trace du dessin en cours --brand-bright
   (jamais le jaune proche de --ble du marqueur de navigation). Sommet blanc
   cercle de sombre, poignee de milieu creuse, cible d'accrochage pleine en
   bleu (le message de dessin demande de cliquer sur un point bleu).
   Le CSS gagne sur l'attribut stroke que Leaflet pose sur le trace. */
.mod-poly { stroke: #FFFFFF; }
.mod-poly.is-selected { stroke: var(--brand); }
.mod-draft { stroke: var(--brand-bright); }

/* Poignees : icone de 44 px (iconSize du script), disque au centre en
   ::after. Au doigt, toute l'icone est la cible (44 px). A la souris, seul
   le disque capte le clic : un clic pres d'un sommet partage, pour
   selectionner la zone voisine, supprimerait sinon ce sommet. Le clic sur
   ::after est rendu a l'icone, que Leaflet ecoute. */
.mod-vertex,
.mod-mid {
    display: flex;
    align-items: center;
    justify-content: center;
}
.mod-vertex::after,
.mod-mid::after {
    content: "";
    box-sizing: border-box;
    border-radius: 50%;
}
.mod-vertex::after {
    width: 14px;
    height: 14px;
    background: #FFFFFF;
    border: 2px solid var(--glass-solid);
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.6), 0 1px 4px rgba(0, 0, 0, 0.4);
}
.mod-mid::after {
    width: 12px;
    height: 12px;
    background: rgba(255, 255, 255, 0.55);
    border: 2px dashed var(--glass-solid);
}
.mod-vertex { cursor: move; }
.mod-mid { cursor: copy; }
/* (0,3,0) : gagne sur .leaflet-marker-icon.leaflet-interactive de leaflet.css */
.leaflet-marker-icon.leaflet-interactive.mod-vertex,
.leaflet-marker-icon.leaflet-interactive.mod-mid { pointer-events: none; }
.leaflet-marker-icon.leaflet-interactive.mod-vertex::after,
.leaflet-marker-icon.leaflet-interactive.mod-mid::after { pointer-events: auto; }
@media (pointer: coarse) {
    .leaflet-marker-icon.leaflet-interactive.mod-vertex,
    .leaflet-marker-icon.leaflet-interactive.mod-mid { pointer-events: auto; }
}
.mod-snap {
    width: 12px;
    height: 12px;
    box-sizing: border-box;
    background: var(--brand-bright);
    border: 2px solid #FFFFFF;
    border-radius: 50%;
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.45);
}
.leaflet-container.mod-drawing { cursor: crosshair; }

/* Etiquette permanente de dose au centre d'une zone, en glass */
.leaflet-tooltip.mod-tip {
    padding: 4px 8px;
    background: var(--glass);
    border: 1px solid var(--glass-line);
    border-radius: var(--r-sm);
    box-shadow: none;
    color: var(--glass-ink);
    font-family: var(--mono);
    font-size: 12px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    line-height: 1.3;
    white-space: nowrap;
}
.leaflet-tooltip.mod-tip::before { display: none; }

/* ---------- Tablette et mobile (<= 1024 px, meme seuil que le tiroir) ----
   Panneau bas pleine largeur, au plus la moitie de la carte. Deux colonnes :
   la colonne de dessin reste en place (sticky) quand la liste des zones
   fait defiler le panneau. */
@media (max-width: 1024px) {
    .mod-panel {
        top: auto;
        left: var(--dash-marge);
        right: var(--dash-marge);
        /* Laisse lisible l'attribution des tuiles, comme le bandeau live */
        bottom: 20px;
        width: auto;
        max-height: 50%;
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        align-items: start;
        column-gap: 16px;
    }
    .mod-panel__edit {
        position: sticky;
        top: 0;
    }
}

/* Telephone : une colonne, tout le panneau defile */
@media (max-width: 600px) {
    .mod-panel {
        display: flex;
        align-items: stretch;
    }
    .mod-panel__edit { position: static; }
}

/* Telephone a l'horizontale : panneau a droite sur la hauteur de la carte */
@media (max-width: 1024px) and (max-height: 480px) {
    .mod-panel {
        top: var(--dash-marge);
        left: auto;
        bottom: auto;
        width: 320px;
        max-height: calc(100% - 52px);
        display: flex;
        align-items: stretch;
    }
    .mod-panel__edit { position: static; }
}

@media (prefers-reduced-motion: reduce) {
    .mod-input,
    .mod-zone,
    .mod-zone-del { transition: none; }
}

/* ==========================================================================
   5. PARAMETRES (parametres.html)
   --------------------------------------------------------------------------
   Page qui defile dans le <main> du shell : trois onglets C4 (role=tab), une
   carte C5 par panneau, 760 px au plus.

      +--------------------------------------------+
      |  [ Carte ]     [ Camera ]     [ Compte ]   |   onglets C4, 3 colonnes
      +--------------------------------------------+
      | Palette                                    |   #carte : palette
      |  mode   [ % | dosage ]                     |   #camera : URL du flux
      |  couche [Rangs|D1..D8]                     |   #compte : identifiant,
      |  apercu de la legende                      |             mot de passe
      |  seuils (grille)                           |
      +--------------------------------------------+

   Classes et identifiants lus par js/parametres.js : .param-onglets
   (role=tablist, data-onglet et aria-controls des onglets), .sf-param-tab,
   .sf-param-legend (.sf-legend-bar, .sf-legend-scale et les classes
   is-gauche / is-droite / is-masquee des graduations), .sf-param-unit,
   .param-msg (--ok, --err), .param-erreur ; aria-selected des onglets,
   hidden des panneaux, aria-pressed des boutons .seg et aria-invalid des
   champs (seuils, URL, mot de passe) sont poses par le script.
   ========================================================================== */
.app-shell > main.param { padding: 24px; }

.param__head { margin-bottom: 16px; }
.param__title {
    /* Niveau XL · h1 de l'echelle A4 (34 / 700 / -0.025em), celui des noms
       de page ; niveau L (28 px) sous 480 px */
    font-family: var(--display);
    font-size: 34px;
    font-weight: 700;
    letter-spacing: -0.025em;
    line-height: 1.1;
}
.param__sub {
    margin-top: 4px;
    font-size: 14px;
    color: var(--ink-soft);
}

/* ---------- Onglets C4 ----------
   Trois colonnes egales a toutes les largeurs : l'auto-fit de la charte
   (150 px au moins) poserait Compte seul sur une seconde ligne sous 480 px.
   Dessin de la charte (icone au-dessus du libelle, 77 px de haut) ; actif
   par aria-selected (components.css). */
.param-onglets,
.param-panneau { max-width: 760px; }
.param-onglets {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    margin-bottom: 16px;
}
/* Libelle plus large que sa colonne : il passe a la ligne, sans deborder */
.param-onglets > .filter-tab { min-width: 0; text-align: center; }

/* ---------- Carte C5 ----------
   Pleine largeur de son panneau (480 px dans la charte). Le survol de la
   charte signale une carte cliquable : ces cartes ne le sont pas. */
.param-card { max-width: none; }
.param-card:hover { border-color: var(--line); }
.param-card .field { max-width: none; }
.param-card .card__footer { flex-wrap: wrap; }
/* Actions a droite, meme quand la mention passe seule sur sa ligne */
.param-card .card__footer-actions { margin-left: auto; }

.param-corps {
    padding: 16px;
    display: flex;
    flex-direction: column;
    gap: 16px;
}

/* Libelle mono en capitales, dessin de .field__label (B3). ECART : en
   --ink-soft (sous-libelles, A2) et non --muted (3,8:1) : il porte le sens
   des champs ("Sous-dosage · ecart negatif" au-dessus de "Seuil 1"). */
.param-label {
    font-family: var(--mono);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-soft);
}
.param-aide {
    font-size: 13px;
    line-height: 1.4;
    color: var(--ink-soft);
}

/* Message d'une carte (role=status ou alert). Vide, il reste dans la page :
   une region live qui apparait deja remplie n'est pas annoncee. Il rend
   seulement l'ecart qui le precede. */
.param-msg {
    font-size: 14px;
    line-height: 1.4;
    color: var(--ink-soft);
}
.param-corps > .param-msg:empty { margin-top: -16px; }
/* Lisere et fond portent la couleur, le texte reste en --ink : --danger sur
   sa propre teinte ne fait que 3,8:1 en clair (meme regle que .login__error) */
.param-msg--ok,
.param-msg--err {
    padding: 12px 16px;
    border-left: 4px solid var(--brand);
    border-radius: var(--r-md);
    background: var(--brand-soft);
    color: var(--ink);
    font-weight: 600;
}
.param-msg--err {
    border-left-color: var(--danger);
    background: var(--danger-soft);
}

/* ---------- Palette ---------- */
.param-ligne {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 16px;
}
/* L'aide du mode se lit avec la bascule qui la precede */
.param-ligne + .param-aide { margin-top: -8px; }
/* Neuf onglets : ils passent a la ligne dans le cadre du bouton segmente
   plutot que de defiler, un defilement horizontal cacherait des couches */
.param-couches { flex-wrap: wrap; }

.param-apercu {
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.sf-param-legend {
    padding: 12px 16px 8px;
    background: var(--bg-2);
    border: 1px solid var(--line);
    border-radius: var(--r-md);
}
/* Fond pose par buildPreview ; --bg-3 le temps de lire la configuration */
.sf-param-legend .sf-legend-bar {
    height: 16px;
    background: var(--bg-3);
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
}
/* Graduations sous leur borne, comme au tableau de bord (section 3) */
.sf-param-legend .sf-legend-scale {
    position: relative;
    height: 16px;
    margin-top: 4px;
    font-family: var(--mono);
    font-size: 11px;
    font-weight: 500;
    line-height: 16px;
    font-variant-numeric: tabular-nums;
    color: var(--ink-soft);
}
.sf-param-legend .sf-legend-scale > span {
    position: absolute;
    top: 0;
    white-space: nowrap;
    transform: translateX(-50%);
}
.sf-param-legend .sf-legend-scale > :first-child,
.sf-param-legend .sf-legend-scale > .is-droite { transform: none; }
.sf-param-legend .sf-legend-scale > :last-child,
.sf-param-legend .sf-legend-scale > .is-gauche { transform: translateX(-100%); }
.sf-param-legend .sf-legend-scale > .is-masquee { visibility: hidden; }

.param-groupe {
    min-width: 0;
    border: none;
}
/* Quatre colonnes fixes dans chaque groupe : les champs des trois groupes
   restent alignes (Seuil 1 sous Sous la cible). Deux sous 640 px. */
.param-seuils {
    margin-top: 8px;
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 12px;
}
.param-seuil .field__input-wrap {
    padding: 0 12px;
    gap: 8px;
}
.param-seuil__pastille {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    border-radius: var(--r-sm);
    /* Filet interne --line-strong : detache le jaune du fond clair et le
       bleu ou le violet du fond sombre */
    box-shadow: inset 0 0 0 1px var(--line-strong);
}
.param-seuil__input {
    width: 0;
    text-align: right;
    font-family: var(--mono);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    -moz-appearance: textfield;
}
/* Fleches du champ nombre masquees : sur 52 px de large, elles couvrent les
   chiffres. Les fleches du clavier gardent le pas de 0,5. */
.param-seuil__input::-webkit-outer-spin-button,
.param-seuil__input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}
.param-seuil__unite {
    flex-shrink: 0;
    min-width: 16px;
    font-size: 13px;
    font-weight: 500;
    color: var(--ink-soft);
}
/* Palier extreme : une couleur sans seuil a regler, pas un champ */
.param-seuil__fixe {
    height: 56px;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 0 12px;
    border: 1.5px dashed var(--line-strong);
    border-radius: var(--r-md);
    font-size: 14px;
    color: var(--ink-soft);
}

/* ---------- Camera ---------- */
/* Adresse et port : chiffres en Plex Mono */
.param-mono {
    font-family: var(--mono);
    font-variant-numeric: tabular-nums;
}

/* ---------- Compte ---------- */
.param-compte {
    padding: 16px;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px 16px;
}
.param-compte__qui {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}
/* Role en lecture seule : badge a sa largeur, a cote de l'identifiant */
.param-compte__qui.param-compte__role { flex: 0 0 auto; align-items: flex-start; }
/* Identifiant en Plex Mono (charte, A4) */
.param-compte__nom {
    font-family: var(--mono);
    font-size: 16px;
    font-weight: 600;
    overflow-wrap: anywhere;
}
/* Deconnexion : --danger au survol, comme dans la sidebar (charte, A3).
   Le libelle reste en --ink : --danger sur --danger-soft fait 3,8:1 (regle
   de .login__error) ; bordure, fond et icone portent la couleur. */
.param-compte [data-sf-deconnexion]:hover {
    background: var(--danger-soft);
    border-color: var(--danger);
    color: var(--ink);
}
.param-compte [data-sf-deconnexion]:hover svg { color: var(--danger); }

.param-mdp { border-top: 1px solid var(--line); }
.param-mdp__titre {
    font-family: var(--display);
    font-size: 16px;
    font-weight: 700;
    letter-spacing: -0.01em;
}
/* --danger sur --bg : 4,7:1 dans les deux themes */
.param-erreur {
    font-size: 13px;
    font-weight: 600;
    line-height: 1.4;
    color: var(--danger);
}
.param-erreur:empty { display: none; }
.param-card .field__input-wrap:has(> [aria-invalid="true"]) { border-color: var(--danger); }

/* Quatre champs de 128 px au moins (52 px de chiffres) ne tiennent plus */
@media (max-width: 640px) {
    .param-seuils { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 480px) {
    .app-shell > main.param { padding: 16px; }
    .param__title { font-size: 28px; }
}

/* ==========================================================================
   6. CHAMPS (champs.html)
   --------------------------------------------------------------------------
   Desktop (> 1024 px) : colonne de 320 a 384 px a gauche de la carte. La
   colonne montre la liste, ou a sa place l'editeur ou l'import.

      +--------------+-----------------------------------+
      | Champs       | [+][-]                            |
      | 3 CHAMPS ... | [sat][plan]                       |
      | [+ Nouveau]  |                                   |
      | [Importer]   |        polygones, etiquettes      |
      | [recherche]  |        (zoom >= 14)               |
      |--------------|                                   |
      | carte C6     |                                   |
      | carte C6     |                                   |
      +--------------+-----------------------------------+

   Tablette et mobile (<= 1024 px) : bascule Liste | Carte en tete ; la
   liste recouvre la carte (data-vue="liste") ou disparait (data-vue="carte").
   En edition et en import (data-mode), la bascule disparait, la carte est
   affichee et le panneau passe en bas en glass sombre.

      +---------------------------+   +---------------------------+
      | [ Liste |  Carte ]        |   | [+][-]                    |
      +---------------------------+   | [sat][plan]               |
      | Champs                    |   |                           |
      | [+ Nouveau] [Importer]    |   | +-----------------------+ |
      | carte C6                  |   | | panneau bas (glass)   | |
      +---------------------------+   | +-----------------------+ |
                                      +---------------------------+

   Le panneau (editeur, import) est le meme balisage dans les deux cas :
   ses couleurs passent par les variables --cp-*, jetons de page sur desktop
   (theme clair ou sombre), jetons glass sous 1024 px (sombre dans les deux
   themes, comme tout panneau pose sur la carte).

   Classes posees par js/champs.js : .champs-card (et .is-selected, .is-coche),
   .champs-card__select, __case, __titres, __meta, __metrics, __actions, __suppr ;
   filtre dossiers .champs-dossiers (__puces, __actions, __gerer, __suppr),
   .champs-puce (__nom, __compte, .is-active) ; barre groupee .champs-groupe
   (__tout, __compteur, __actions, __suppr) ; selecteur .champs-modale, .champs-select ;
   .champs-etat, .champs-squelettes, .champs-squelette ; .champs-ligne (et
   .is-coche, .is-doublon, --ignoree), __case, __apercu, __nom, __surface,
   __icone, __texte, __titre, __raison ; .champs-tag-doublon,
   .champs-avertissement ; .champs-msg--err, --info ; calques Leaflet
   .champ-poly, .champ-etiquette, .champ-trace (et .is-ouvert, .is-invalide,
   .is-fige), .champ-sommet (et .is-serre, .is-dernier), .champ-milieu,
   .champ-accroche, .champ-import (et .is-coche, .is-survol) ;
   .champs-sans-etiquettes sur la carte ; .is-edition, .is-dessin et
   .is-import sur #champsCarte. Apercus SVG : .apercu, .apercu--vide,
   .apercu__forme (js/preview.js).
   ========================================================================== */
.app-shell > main.champs {
    display: grid;
    grid-template-columns: minmax(320px, 384px) minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr);
    grid-template-areas: "colonne carte";
}
.champs-bascule { display: none; }

.champs-liste,
.champs-panneau {
    grid-area: colonne;
    min-width: 0;
    min-height: 0;
    border-right: 1px solid var(--line);
}
.champs-carte {
    grid-area: carte;
    position: relative;
    min-width: 0;
    min-height: 0;
    /* Isole les z-index de Leaflet : le panneau bas passe au-dessus */
    isolation: isolate;
}

/* ---------- Liste ---------- */
.champs-liste {
    /* Repere de offsetTop des cartes (defilement jusqu'a la selection) */
    position: relative;
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: thin;
}
/* En-tete colle en haut : Nouveau champ et la recherche restent a portee
   quand la liste defile */
.champs-liste__head {
    position: sticky;
    top: 0;
    z-index: 2;
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 24px 16px 16px;
    background: var(--app-canvas);
    border-bottom: 1px solid var(--line);
}
/* Niveau XL (h1) de l'echelle A4, comme .param__title (28 px sous 480 px) */
.champs__title {
    font-family: var(--display);
    font-size: 34px;
    font-weight: 700;
    letter-spacing: -0.025em;
    line-height: 1.1;
}
.champs-resume {
    min-height: 20px;
    margin-top: -8px;
    font-family: var(--mono);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    font-variant-numeric: tabular-nums;
    color: var(--ink-soft);
}
.champs-liste__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}
.champs-liste__actions .btn {
    flex: 1 1 auto;
    padding: 0 16px;
}
.champs-recherche { max-width: none; }
/* 16 px comme B3 : en dessous, Safari iOS zoome la page au focus */
.champs-recherche input { font-size: 16px; }
/* Croix native de type=search masquee : le bouton Effacer (44 px, nomme)
   la remplace */
.champs-recherche input::-webkit-search-cancel-button {
    -webkit-appearance: none;
    appearance: none;
}

/* ---------- Filtre par dossier (puces) ----------
   Puces « Tous » / « Sans dossier » / une par dossier (nom + compteur). L'active
   prend --brand comme la selection ; creer/renommer/supprimer a droite. */
.champs-dossiers {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
}
.champs-dossiers__puces {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}
.champs-puce {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 32px;
    padding: 0 12px;
    background: var(--bg-2);
    border: 1px solid var(--line-strong);
    /* Charte SoftiROB : carre arrondi facon DATUM, jamais la pilule iOS (999px). */
    border-radius: var(--r-md);
    color: var(--ink-soft);
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    transition: border-color 0.12s ease, background 0.12s ease, color 0.12s ease;
}
.champs-puce:hover { border-color: var(--brand); }
.champs-puce.is-active {
    background: var(--brand-softer);
    border-color: var(--brand);
    color: var(--ink);
}
.champs-puce:focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: 2px;
}
.champs-puce__nom {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 12rem;
}
.champs-puce__compte {
    font-family: var(--mono);
    font-size: 11px;
    font-variant-numeric: tabular-nums;
    color: var(--muted-ink);
}
.champs-puce.is-active .champs-puce__compte { color: var(--brand); }
.champs-dossiers__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}
.champs-dossiers__suppr:hover {
    background: var(--danger-soft);
    border-color: var(--danger);
    color: var(--danger);
}

/* ---------- Barre d'actions groupees (multi-selection) ---------- */
.champs-groupe {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 6px 12px;
    background: var(--brand-softer);
    border: 1px solid var(--brand-soft);
    border-radius: var(--r-md);
}
.champs-groupe__tout { flex-shrink: 0; }
.champs-groupe__compteur {
    flex: 1 1 auto;
    min-width: 0;
    font-family: var(--mono);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.04em;
    font-variant-numeric: tabular-nums;
    color: var(--ink);
}
.champs-groupe__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}
.champs-groupe__suppr:hover {
    background: var(--danger-soft);
    border-color: var(--danger);
    color: var(--danger);
}

/* ---------- Selecteur « Deplacer vers un dossier » ----------
   Panneau maison (un <select>, que SF.ui.form ne construit pas) : meme fond que
   les dialogues de la charte (.modal-backdrop / .modal). Cache par [hidden]. */
.champs-modale:not([hidden]) {
    position: fixed;
    inset: 0;
    z-index: var(--z-modal);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
    background: rgba(0, 0, 0, 0.45);
    animation: sf-fondu 0.12s ease-out;
}
/* <select> pose dans un .field__input-wrap : il occupe le cadre, la fleche
   native indique le menu deroulant */
.champs-select { cursor: pointer; }

.champs-cartes { padding: 16px; }
.champs-cartes__liste {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 12px;
}
.champs-etat { padding: 32px 16px; }

.champs-squelettes {
    display: flex;
    flex-direction: column;
    gap: 12px;
}
.champs-squelette {
    height: 144px;
    border-radius: var(--r-md);
    background: var(--bg-3);
    animation: champs-pulsation 1.2s ease-in-out infinite alternate;
}
@keyframes champs-pulsation { from { opacity: 0.45; } to { opacity: 0.9; } }

/* ---------- Carte de champ (C6) ----------
   Pleine largeur de la colonne (280 px dans la charte), padding 12 px et
   ecart 8 px au lieu de 16 et 12 : quatre cartes visibles sur un ecran de
   900 px. Le nom est un bouton etire sur toute la carte (::after) : la
   carte entiere selectionne, les boutons Modifier et Supprimer restent des
   cibles distinctes posees au-dessus. */
.champs-card {
    position: relative;
    width: auto;
    padding: 12px 12px 12px 16px;
    gap: 8px;
    transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
/* Selection DATUM : barre epaisse a gauche + voile leger, pas d'anneau.
   La carte garde son filet --line ; le 16 px de padding gauche laisse 10 px
   entre la barre et le contenu. */
.champs-card.is-selected {
    box-shadow: inset 6px 0 0 var(--brand);
    background: var(--brand-softer);
}
.champs-card .field-card__head {
    align-items: flex-start;
    gap: 12px;
}
.champs-card__titres {
    min-width: 0;
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    gap: 4px;
}
/* Case de multi-selection : au-dessus du bouton de selection (::after, inset 0)
   comme les actions, pour rester cliquable. Alignee sur le nom en haut. */
.champs-card__case {
    position: relative;
    z-index: 1;
    flex-shrink: 0;
    min-height: 0;
    margin-top: -2px;
}
/* Champ coche : bord de marque (cumulable avec .is-selected) */
.champs-card.is-coche { border-color: var(--brand); }
.champs-card .field-card__name {
    line-height: 1.25;
    overflow-wrap: anywhere;
}
.champs-card__select {
    padding: 0;
    border: none;
    background: none;
    font: inherit;
    letter-spacing: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
}
.champs-card__select::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: var(--r-md);
}
/* Anneau de focus autour de toute la carte, pas du seul nom */
.champs-card__select:focus-visible { outline: none; }
.champs-card__select:focus-visible::after {
    outline: 2px solid var(--brand);
    outline-offset: 2px;
}
.champs-card__meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px 8px;
    font-family: var(--mono);
    font-size: 11px;
    letter-spacing: 0.04em;
    font-variant-numeric: tabular-nums;
    color: var(--muted-ink);
}
.champs-card .field-card__shape { flex-shrink: 0; }
.champs-card__metrics {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: end;
    gap: 8px;
    padding-top: 8px;
}
.champs-card__actions {
    position: relative;
    z-index: 1;
    display: flex;
    gap: 8px;
}
/* Suppression : --danger au survol, comme la corbeille de la modulation */
.champs-card__suppr:hover {
    background: var(--danger-soft);
    border-color: var(--danger);
    color: var(--danger);
}

/* ---------- Apercu SVG d'un contour (js/preview.js) ----------
   Couleurs de la charte (C6 : remplissage --brand a 30 %, contour --brand),
   trait constant quelle que soit la taille de la vignette. */
.apercu {
    display: block;
    width: 100%;
    height: 100%;
}
.apercu__forme {
    fill: var(--brand);
    fill-opacity: 0.3;
    stroke: var(--brand);
    stroke-width: 1.5px;
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
}
.apercu--vide .apercu__forme {
    fill: none;
    stroke: var(--muted);
}

/* ---------- Panneau : editeur et import ---------- */
.champs-panneau {
    --cp-fond: var(--app-canvas);
    --cp-texte: var(--ink);
    --cp-texte-doux: var(--ink-soft);
    --cp-attenue: var(--muted-ink);
    --cp-filet: var(--line);
    --cp-saisie: var(--bg);
    --cp-saisie-bord: var(--line-strong);
    --cp-saisie-h: 56px;
    --cp-bouton-h: 52px;
    --cp-anneau: var(--brand-soft);
    --cp-ligne: var(--bg);
    --cp-ligne-bord: var(--line);
    --cp-ghost: var(--bg);
    --cp-ghost-bord: var(--line-strong);
    --cp-ghost-survol: var(--bg-2);
    --cp-case-bord: var(--muted);
    --cp-info: var(--brand-soft);
    --cp-avert: var(--warning-ink);
    --cp-apercu: var(--brand-soft);
    display: flex;
    flex-direction: column;
    background: var(--cp-fond);
    color: var(--cp-texte);
}
.champs-panneau__head {
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 24px 16px 12px;
    border-bottom: 1px solid var(--cp-filet);
}
/* Niveau L de l'echelle A4 : le panneau remplace la liste et son h1 */
.champs-panneau__title {
    font-family: var(--display);
    font-size: 28px;
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1.15;
}
/* Focus pose par script a l'ouverture (point de depart du clavier) */
.champs-panneau__title:focus { outline: none; }
.champs-panneau__aide {
    font-size: 14px;
    line-height: 1.45;
    color: var(--cp-texte-doux);
}
.champs-import__fichier {
    font-family: var(--mono);
    font-size: 13px;
    overflow-wrap: anywhere;
}
.champs-panneau__corps {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 16px;
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: thin;
}
.champs-panneau__pied {
    flex-shrink: 0;
    display: flex;
    gap: 8px;
    padding: 12px 16px;
    border-top: 1px solid var(--cp-filet);
}
.champs-panneau__pied .btn {
    flex: 1 1 0;
    min-width: 0;
    padding: 0 12px;
}
/* Action principale deux fois plus large qu'Annuler : « Importer 120
   champs » tient sur une ligne a 400 px comme dans la colonne desktop */
.champs-panneau__pied .btn--primary { flex-grow: 2; }
.champs-panneau .btn:not(.btn--small) { height: var(--cp-bouton-h); }
/* Ghost de la charte transpose sur glass (meme regle que la modulation) */
.champs-panneau .btn--ghost {
    background: var(--cp-ghost);
    border-color: var(--cp-ghost-bord);
    color: var(--cp-texte);
}
.champs-panneau .btn--ghost:hover:not(:disabled) { background: var(--cp-ghost-survol); }

/* Champ de saisie : dessin de B3 (bordure 1,5 px, anneau de 4 px au focus) */
.champs-champ {
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.champs-champ__label {
    font-family: var(--mono);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--cp-texte-doux);
}
.champs-input {
    width: 100%;
    min-width: 0;
    height: var(--cp-saisie-h);
    padding: 0 16px;
    background: var(--cp-saisie);
    border: 1.5px solid var(--cp-saisie-bord);
    border-radius: var(--r-md);
    color: var(--cp-texte);
    /* 16 px : en dessous, Safari iOS zoome la page au focus */
    font-size: 16px;
    font-weight: 500;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.champs-input:focus {
    border-color: var(--brand);
    box-shadow: 0 0 0 4px var(--cp-anneau);
}
.champs-input:focus-visible { outline: none; }
.champs-input[aria-invalid="true"] { border-color: var(--danger); }
.champs-input:disabled { opacity: 0.6; }

/* Statistiques du contour et outils sur une ligne */
.champs-contour {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}
.champs-stats {
    font-family: var(--mono);
    font-size: 13px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    font-variant-numeric: tabular-nums;
    color: var(--cp-texte);
}
.champs-outils {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

/* Message (role=status). Vide, il reste dans la page (region live annoncee
   a sa prochaine ecriture) mais rend l'ecart qui le precede. Erreur et
   information dessinees comme .param-msg : lisere et fond en couleur,
   texte --cp-texte. */
.champs-msg {
    font-size: 14px;
    line-height: 1.4;
    color: var(--cp-texte-doux);
}
.champs-panneau__corps > .champs-msg:empty { margin-top: -12px; }
.champs-msg--err,
.champs-msg--info {
    padding: 12px 16px;
    border-left: 4px solid var(--danger);
    border-radius: var(--r-md);
    background: var(--danger-soft);
    color: var(--cp-texte);
    font-weight: 600;
    overflow-wrap: anywhere;
}
.champs-msg--info {
    border-left-color: var(--brand);
    background: var(--cp-info);
}

/* ---------- Import ---------- */
.champs-import__analyse {
    display: flex;
    align-items: center;
    gap: 12px;
    min-height: var(--tap);
    font-size: 14px;
    color: var(--cp-texte-doux);
    overflow-wrap: anywhere;
}
.champs-roue {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
    border: 2px solid var(--cp-filet);
    border-top-color: var(--brand);
    border-radius: 50%;
    animation: champs-rotation 0.8s linear infinite;
}
@keyframes champs-rotation { to { transform: rotate(360deg); } }

.champs-import__apercu {
    display: flex;
    flex-direction: column;
    gap: 12px;
}
.champs-avertissements {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.champs-avertissements:empty { display: none; }
.champs-avertissement {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 8px 12px;
    border: 1px solid var(--cp-avert);
    border-radius: var(--r-md);
    font-size: 13px;
    line-height: 1.4;
    color: var(--cp-texte);
}
.champs-avertissement .icon {
    width: 18px;
    height: 18px;
    color: var(--cp-avert);
}
.champs-import__outils {
    display: flex;
    justify-content: flex-start;
}

/* Lignes de 44 px : case, apercu, nom modifiable, surface ; tag "Existe
   deja" sous le nom */
.champs-lignes {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.champs-lignes:empty { display: none; }
.champs-ligne {
    display: grid;
    grid-template-columns: 44px 36px minmax(0, 1fr) auto;
    grid-template-areas: "case apercu nom surface";
    align-items: center;
    column-gap: 8px;
    min-height: var(--tap);
    padding-right: 12px;
    background: var(--cp-ligne);
    border: 1px solid var(--cp-ligne-bord);
    border-radius: var(--r-md);
    transition: border-color 0.12s ease;
}
.champs-ligne.is-coche { border-color: var(--brand); }
.champs-ligne.is-doublon {
    grid-template-areas:
        "case apercu nom surface"
        "case apercu tag surface";
    padding-bottom: 8px;
}
.champs-ligne__case {
    grid-area: case;
    width: 44px;
    justify-content: center;
}
/* DATUM 2b : la boite reste transparente (le fond du panneau transparait), on
   n'adapte que la couleur du bord au glass sombre. Un fond --cp-case opaque
   ici masquait la signature << boite transparente >> a son seul lieu de rendu. */
.champs-panneau .checkbox__box {
    border-color: var(--cp-case-bord);
}
.champs-ligne__apercu {
    grid-area: apercu;
    width: 36px;
    height: 36px;
    border-radius: var(--r-sm);
    background: var(--cp-apercu);
}
/* Nom modifiable : cadre visible au survol et au focus seulement, la ligne
   se lit comme une liste */
.champs-ligne__nom {
    grid-area: nom;
    height: var(--tap);
    padding: 0 8px;
    background: transparent;
    border-color: transparent;
}
.champs-ligne__nom:hover { border-color: var(--cp-saisie-bord); }
.champs-ligne__surface {
    grid-area: surface;
    font-family: var(--mono);
    font-size: 13px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    color: var(--cp-texte);
}
/* Non cochee : apercu et surface attenues, le nom reste lisible */
.champs-ligne:not(.is-coche) .champs-ligne__apercu,
.champs-ligne:not(.is-coche) .champs-ligne__surface { opacity: 0.55; }
/* Doublon : lisere d'avertissement, texte --cp-texte (--warning sur fond
   clair : 2,25:1) */
.tag.champs-tag-doublon {
    grid-area: tag;
    justify-self: start;
    margin-left: 8px;
    background: transparent;
    border: 1px solid var(--cp-avert);
    color: var(--cp-texte);
}
.champs-ligne--ignoree {
    grid-template-columns: 44px minmax(0, 1fr);
    grid-template-areas: none;
    padding: 8px 12px 8px 0;
    background: transparent;
    border-style: dashed;
}
.champs-ligne__icone {
    display: flex;
    justify-content: center;
    color: var(--cp-attenue);
}
.champs-ligne__texte {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.champs-ligne__titre {
    font-size: 14px;
    font-weight: 600;
    color: var(--cp-texte-doux);
    overflow-wrap: anywhere;
}
.champs-ligne__raison {
    font-size: 13px;
    line-height: 1.4;
    color: var(--cp-attenue);
}

/* ---------- Calques Leaflet ----------
   Couleurs fixes comme les calques de la modulation (posees sur l'image
   satellite) : champs enregistres en --brand-bright (bleu), selection plus
   epaisse et plus remplie ; contour en cours de trace ou d'edition en VIOLET
   (--contour, rouge s'il se croise) ; contours de l'import en pointilles,
   attenues tant qu'ils ne sont pas coches. Le CSS gagne sur les attributs que
   Leaflet pose sur le trace. */
.champ-poly {
    stroke: var(--brand-bright);
    fill: var(--brand);
}
.champ-poly.is-selected {
    stroke-width: 4px;
    fill-opacity: 0.45;
}
.champs-carte.is-edition .champ-poly,
.champs-carte.is-import .champ-poly {
    stroke-opacity: 0.6;
    fill-opacity: 0.1;
}
.champs-carte.is-dessin .leaflet-container,
.champs-carte.is-dessin .leaflet-interactive.champ-poly { cursor: crosshair; }

/* Trace en cours de dessin ou d'edition : VIOLET (--contour, feature « trace de
   contour »), pour le distinguer du bleu des champs enregistres (.champ-poly).
   Trait et remplissage en --contour ; le remplissage garde son fillOpacity
   (pose par Leaflet). Un contour qui se croise reste rouge (.is-invalide). */
.champ-trace {
    stroke: var(--contour);
    fill: var(--contour);
}
.champ-trace.is-ouvert { stroke-dasharray: 8 6; }
.champ-trace.is-invalide {
    stroke: var(--danger-fill);
    fill: var(--danger-fill);
}
.champ-trace.is-fige { stroke-dasharray: 2 6; }

.champ-import {
    stroke: var(--brand-bright);
    stroke-dasharray: 8 6;
    stroke-opacity: 0.5;
    fill: var(--brand-bright);
    fill-opacity: 0.06;
}
.champ-import.is-coche {
    stroke: #FFFFFF;
    stroke-opacity: 1;
    fill-opacity: 0.25;
}
.champ-import.is-survol {
    stroke-width: 4px;
    stroke-dasharray: none;
    stroke-opacity: 1;
    fill-opacity: 0.4;
}

/* Poignees : icone de 44 px (cible tactile), point de 16 px (sommet) ou
   poignee creuse de 12 px (milieu) dessine au centre, comme la modulation.
   A la souris, seul le point capte le clic : un clic pres d'un sommet, qui
   vise la carte, ne le retire pas. Au doigt, toute l'icone, sauf pour un
   sommet serre (.is-serre, voisin a moins de 44 px) : les icones
   couvriraient la carte le long du contour. js/champs.js ne pose pas de
   poignee de milieu sur un cote trop court pour qu'elle soit atteignable. */
.champ-sommet,
.champ-milieu {
    display: flex;
    align-items: center;
    justify-content: center;
}
.champ-sommet::after,
.champ-milieu::after {
    content: "";
    box-sizing: border-box;
    border-radius: 50%;
}
.champ-sommet::after {
    width: 16px;
    height: 16px;
    background: #FFFFFF;
    border: 2px solid var(--glass-solid);
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.6), 0 1px 4px rgba(0, 0, 0, 0.4);
}
.champ-milieu::after {
    width: 12px;
    height: 12px;
    background: rgba(255, 255, 255, 0.55);
    border: 2px dashed var(--glass-solid);
}
.champ-sommet { cursor: move; }
.champ-milieu { cursor: copy; }
.leaflet-marker-icon.leaflet-interactive.champ-sommet,
.leaflet-marker-icon.leaflet-interactive.champ-milieu { pointer-events: none; }
.leaflet-marker-icon.leaflet-interactive.champ-sommet::after,
.leaflet-marker-icon.leaflet-interactive.champ-milieu::after { pointer-events: auto; }
/* Dernier sommet pose pendant le trace : le prochain point s'y rattache.
   Accent du dessin : violet comme le trace (les sommets neutres restent des
   pastilles blanches a bord sombre, lisibles sur toute imagerie). */
.champ-sommet.is-dernier::after {
    background: var(--contour);
    border-color: #FFFFFF;
}
@media (pointer: coarse) {
    .leaflet-marker-icon.leaflet-interactive.champ-sommet:not(.is-serre),
    .leaflet-marker-icon.leaflet-interactive.champ-milieu { pointer-events: auto; }
}
.champ-accroche {
    width: 12px;
    height: 12px;
    box-sizing: border-box;
    /* Accroche du trace : violet, coherent avec --contour du dessin */
    background: var(--contour);
    border: 2px solid #FFFFFF;
    border-radius: 50%;
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.45);
}

/* Etiquette de nom (B8) : tooltip Leaflet permanente, non interactive (le
   clic traverse vers le polygone). Rayon 12 px (16 dans la charte),
   padding 4 px (5), longueur bornee a 240 px. */
.leaflet-tooltip.champ-etiquette {
    max-width: 240px;
    padding: 4px 12px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    background: var(--bg);
    color: var(--ink);
    border: 1px solid var(--brand);
    border-radius: var(--r-lg);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
    font-family: var(--display);
    font-size: 13px;
    font-weight: 700;
    letter-spacing: -0.01em;
    line-height: 1.3;
}
.leaflet-tooltip.champ-etiquette::before { display: none; }
.champs-sans-etiquettes .champ-etiquette { display: none; }

/* ---------- Tablette et mobile (<= 1024 px, meme seuil que le tiroir) ---- */
@media (max-width: 1024px) {
    .app-shell > main.champs {
        grid-template-columns: minmax(0, 1fr);
        grid-template-rows: auto minmax(0, 1fr);
        grid-template-areas:
            "bascule"
            "contenu";
    }
    .champs-bascule {
        grid-area: bascule;
        display: flex;
        padding: 8px 16px;
        background: var(--app-canvas);
        border-bottom: 1px solid var(--line);
    }
    .champs-bascule .seg { flex: 1 1 auto; }
    .champs-bascule .seg__btn { flex: 1 1 0; }
    .app-shell > main.champs:not([data-mode="liste"]) .champs-bascule { display: none; }

    .champs-liste {
        grid-area: contenu;
        z-index: 1;
        border-right: none;
        background: var(--app-canvas);
    }
    .champs-carte { grid-area: contenu; }
    /* La carte reste mise en page sous la liste : Leaflet garde sa taille */
    .app-shell > main.champs[data-vue="carte"] .champs-liste { display: none; }
    .app-shell > main.champs[data-vue="liste"] .champs-carte { visibility: hidden; }
    .champs-liste__head {
        position: static;
        padding: 16px 16px 0;
        border-bottom: none;
    }
    /* Liste pleine largeur : cartes en grille (maquette D6), une colonne
       sur telephone */
    .champs-cartes__liste {
        display: grid;
        grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr));
    }

    /* Panneau bas en glass sombre, au-dessus de l'attribution des tuiles.
       La hauteur laisse voir les controles de carte et le haut du contour. */
    .champs-panneau {
        --cp-fond: var(--glass);
        --cp-texte: var(--glass-ink);
        --cp-texte-doux: var(--glass-ink-soft);
        --cp-attenue: var(--glass-muted);
        --cp-filet: var(--glass-rule);
        --cp-saisie: var(--glass-soft);
        --cp-saisie-bord: var(--glass-muted);
        --cp-saisie-h: var(--tap);
        --cp-bouton-h: var(--tap);
        --cp-anneau: var(--glass-brand);
        --cp-ligne: var(--glass-fill);
        --cp-ligne-bord: var(--glass-rule);
        --cp-ghost: transparent;
        --cp-ghost-bord: var(--glass-line-strong);
        --cp-ghost-survol: var(--glass-fill);
        --cp-case-bord: var(--glass-muted);
        --cp-info: var(--glass-brand);
        --cp-avert: var(--warning);
        --cp-apercu: var(--glass-brand);
        grid-area: contenu;
        position: absolute;
        left: 16px;
        right: 16px;
        bottom: 20px;
        z-index: 2;
        max-width: 520px;
        max-height: min(60%, calc(100% - 144px));
        border: 1px solid var(--glass-line);
        border-radius: var(--r-lg);
        -webkit-backdrop-filter: var(--glass-blur);
        backdrop-filter: var(--glass-blur);
        /* Champs, cases et barre de defilement sombres sur le glass */
        color-scheme: dark;
    }
    .champs-panneau__head { padding: 12px 16px 8px; }
    .champs-panneau__title {
        font-size: 18px;
        letter-spacing: -0.015em;
    }
    .champs-panneau__aide { font-size: 13px; }
    .champs-panneau__corps { padding: 12px 16px; }
}

/* ---------- Champ selectionne, vue Carte (<= 1024 px) ----------
   Barre glass en bas de la carte : nom, surface, Modifier et Supprimer
   (44 px). Masquee sur desktop, ou la liste reste a cote de la carte. */
.champs-selection { display: none; }
@media (max-width: 1024px) {
    .champs-selection:not([hidden]) {
        position: absolute;
        left: 16px;
        right: 16px;
        bottom: 20px;
        z-index: 1;
        max-width: 520px;
        display: flex;
        align-items: center;
        gap: 8px;
        padding: 8px 8px 8px 16px;
        background: var(--glass);
        color: var(--glass-ink);
        border: 1px solid var(--glass-line);
        border-radius: var(--r-lg);
        -webkit-backdrop-filter: var(--glass-blur);
        backdrop-filter: var(--glass-blur);
    }
    .champs-selection__texte {
        flex: 1 1 auto;
        min-width: 0;
        display: flex;
        flex-direction: column;
        gap: 2px;
    }
    .champs-selection__nom {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        font-family: var(--display);
        font-size: 15px;
        font-weight: 700;
        letter-spacing: -0.01em;
    }
    .champs-selection__surface {
        font-family: var(--mono);
        font-size: 13px;
        font-variant-numeric: tabular-nums;
        color: var(--glass-ink-soft);
    }
    .champs-selection__btn {
        background: transparent;
        border-color: var(--glass-line-strong);
        color: var(--glass-ink);
    }
    .champs-selection__btn:hover { background: var(--glass-fill); }
    .champs-selection__suppr:hover {
        background: var(--glass-fill);
        border-color: var(--danger);
        color: var(--danger);
    }
}

/* Telephone a l'horizontale : panneau a droite sur la hauteur de la carte */
@media (max-width: 1024px) and (max-height: 480px) {
    .champs-panneau {
        top: 16px;
        left: auto;
        width: 320px;
        max-height: none;
    }
}

@media (max-width: 480px) {
    .champs__title { font-size: 28px; }
}

@media (prefers-reduced-motion: reduce) {
    .champs-squelette,
    .champs-roue { animation-duration: 3s; }
    .champs-card,
    .champs-input,
    .champs-ligne { transition: none; }
}

/* ==========================================================================
   7. ADMINISTRATION (admin.html)
   --------------------------------------------------------------------------
   Page qui defile, sur le dessin de Parametres (.param, .param-card,
   .param-corps, .param-aide, .param-erreur, .param-msg) : trois cartes C5
   empilees, 1100 px au plus.

      +--------------------------------------------------------+
      | Etat du systeme                           [actualiser] |
      |  version  base live  points  comptes  champs  surface  |  tuiles
      +--------------------------------------------------------+
      | Comptes  3 comptes · 2 actifs       [+ Nouveau compte] |
      |  compte | role | etat | connexion | cree le | actions  |  tableau
      +--------------------------------------------------------+
      | Nouveau compte                                         |
      |  identifiant          | role                           |  deux colonnes,
      |  mot de passe initial | confirmation                   |  une sous 640 px
      +--------------------------------------------------------+

   Sous 1020 px de large, chaque ligne du tableau devient une carte : les
   colonnes a largeur fixe (role, etat, dates, actions) prennent 790 px,
   l'identifiant garde au moins 200 px (tableau de 990 px au plus serre),
   et la marge absorbe l'ecart des polices de repli.

      +---------------------------------------+
      | (DE) dev [VOUS]               [ADMIN] |  identifiant sur toute la
      |      note sur son propre compte       |  largeur moins le badge
      | DERNIERE CONNEXION          CREE LE   |  colonne de droite
      | il y a 2 min                01/09/2026|  de 100 px
      | 16/09/2026 18:28                      |
      | ETAT                    Actif  [==o]  |
      | [Passer operateur]        [cle] [corb]|
      +---------------------------------------+

   Seuils en container queries et non en @media : la largeur utile depend
   de la sidebar (colonne au-dessus de 1024 px, tiroir en dessous), pas de
   la fenetre. Sans container queries, le tableau reste et defile dans sa
   carte.

   Classes posees par js/admin.js : .admin-tuiles, .admin-tuile (__label,
   __valeur, __total, __sous, __sous--alerte, --large), .admin-chargement,
   .admin-etat-vide, .admin-table, .admin-cell (--compte, --role, --etat,
   --connexion, --creation, --actions), .admin-ligne (.is-inactif),
   .admin-compte (__avatar, __qui, __titre, __nom, __note), .admin-etat
   (__texte), .admin-date (--vide, __exacte), .admin-legende,
   .admin-actions (__role, __suppr) ;
   hidden de #adminRefus et #adminContenu, aria-busy des corps de carte et
   des lignes, aria-invalid des champs.
   ========================================================================== */
.admin-grille,
.admin-refus { max-width: 1100px; }
.admin-grille {
    display: flex;
    flex-direction: column;
    gap: 16px;
}
/* Sections de la page pilotees par la sidebar admin (js/shell.js, js/admin.js) :
   « Général » montre l'etat du systeme, « Gestion des comptes » les comptes et
   le nouveau compte. #adminContenu[data-section] (pose par admin.js, « general »
   par defaut dans admin.html) masque les cartes de l'autre section, sans
   rechargement. */
.admin-grille[data-section="general"] .admin-comptes,
.admin-grille[data-section="general"] .admin-nouveau,
.admin-grille[data-section="general"] .admin-robots,
.admin-grille[data-section="comptes"] .admin-systeme,
.admin-grille[data-section="comptes"] .admin-robots,
.admin-grille[data-section="robots"] .admin-systeme,
.admin-grille[data-section="robots"] .admin-comptes,
.admin-grille[data-section="robots"] .admin-nouveau { display: none; }
/* Titres qui recoivent le focus par script (refus, ligne supprimee, ou
   changement de section par la sidebar admin) : pas des controles, pas
   d'anneau. #systemeTitre et #comptesTitre recoivent tous deux le focus a
   l'activation d'une section : meme regle pour ne pas differer au clavier
   (sinon seul #systemeTitre montrerait l'anneau :focus-visible). */
.admin-refus .empty-state__title:focus,
.admin-systeme .card__title:focus,
.admin-comptes .card__title:focus,
.admin-robots .card__title:focus { outline: none; }

/* Rechargement : contenu garde et attenue, apres 300 ms seulement (une
   reponse rapide ne fait pas clignoter la carte) */
.admin-systeme__corps,
.admin-comptes__corps,
.admin-robots__corps,
.admin-ligne { transition: opacity 0.15s ease; }
.admin-systeme__corps[aria-busy="true"],
.admin-comptes__corps[aria-busy="true"],
.admin-robots__corps[aria-busy="true"],
.admin-ligne[aria-busy="true"] {
    opacity: 0.6;
    transition: opacity 0.15s ease 0.3s;
}

.admin-chargement {
    padding: 24px 16px;
    font-size: 14px;
    color: var(--ink-soft);
}
.admin-etat-vide { margin: 16px; }

/* ---------- Etat du systeme ----------
   Quatre tuiles sur une ligne, ou deux colonnes avec la version et la base live
   sur toute la largeur : jamais une tuile seule a cote d'un trou.
   Colonnes par container query : « LECTURE-ECRITURE » (141 px) doit tenir
   dans sa tuile. */
.admin-systeme { container-type: inline-size; }
.admin-tuiles {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
    padding: 16px;
}
@container (min-width: 720px) {
    .admin-tuiles { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@container (max-width: 559px) {
    .admin-tuiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    /* Version et base live : textes longs, en tete sur toute la largeur */
    .admin-tuile--large { grid-column: 1 / -1; }
}
.admin-tuile .badge { white-space: nowrap; }
.admin-tuile {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 12px;
    background: var(--bg-2);
    border: 1px solid var(--line);
    border-radius: var(--r-md);
}
/* Dessin de .card-metric__label (C5) */
.admin-tuile__label {
    font-family: var(--mono);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--muted-ink);
}
/* Chiffres en Plex Mono, comme .card-metric__value (21-CHARTE §4.2) */
.admin-tuile__valeur {
    min-height: 28px;
    display: flex;
    align-items: center;
    font-family: var(--mono);
    font-size: 20px;
    font-weight: 700;
    letter-spacing: -0.015em;
    font-variant-numeric: tabular-nums;
    color: var(--ink);
    overflow-wrap: anywhere;
}
.admin-tuile__total {
    font-size: 14px;
    font-weight: 500;
    color: var(--ink-soft);
}
.admin-tuile__sous {
    font-size: 12px;
    line-height: 1.4;
    color: var(--ink-soft);
    overflow-wrap: anywhere;
}
.admin-tuile__sous--alerte {
    font-weight: 600;
    color: var(--warning-ink);
}

/* ---------- Comptes ---------- */
.admin-comptes__corps {
    container-type: inline-size;
    overflow-x: auto;
}
.admin-comptes__ajout { white-space: nowrap; }

.admin-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 14px;
}
.admin-table th[scope="col"] {
    padding: 10px 12px;
    background: var(--bg-2);
    border-bottom: 1px solid var(--line);
    text-align: left;
    white-space: nowrap;
    font-family: var(--mono);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--muted-ink);
}
.admin-table th[scope="col"].admin-cell--actions { text-align: right; }
.admin-ligne > .admin-cell {
    padding: 10px 12px;
    border-bottom: 1px solid var(--line);
    text-align: left;
    vertical-align: middle;
    font-weight: 400;
}
.admin-table th[scope="col"]:first-child,
.admin-ligne > .admin-cell:first-child { padding-left: 16px; }
.admin-table th[scope="col"]:last-child,
.admin-ligne > .admin-cell:last-child { padding-right: 16px; }
.admin-ligne:last-child > .admin-cell { border-bottom: none; }

/* Identifiant jamais ecrase par les colonnes a largeur fixe : 14 caracteres
   mono tiennent sur une ligne */
.admin-table .admin-cell--compte { min-width: 200px; }
/* Avatar en haut : la note du compte courant passe sur plusieurs lignes */
.admin-compte {
    min-width: 0;
    display: flex;
    align-items: flex-start;
    gap: 12px;
}
.admin-compte__qui {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.admin-compte__titre {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px 8px;
}
/* Identifiant en Plex Mono, comme dans la carte Compte de Parametres */
.admin-compte__nom {
    font-family: var(--mono);
    font-size: 15px;
    font-weight: 600;
    color: var(--ink);
    overflow-wrap: anywhere;
}
.admin-compte__note {
    max-width: 34ch;
    font-size: 12px;
    line-height: 1.4;
    color: var(--muted-ink);
}
/* Compte desactive : identifiant et avatar attenues (--ink-soft sur --bg-3 :
   7:1 en clair, 6,5:1 en sombre), l'interrupteur porte l'etat */
.is-inactif .admin-compte__nom { color: var(--ink-soft); }
.is-inactif .admin-compte__avatar {
    background: var(--bg-3);
    color: var(--ink-soft);
}

.admin-etat {
    display: flex;
    align-items: center;
    gap: 12px;
}
.admin-etat__texte {
    font-size: 14px;
    font-weight: 600;
    color: var(--ink-soft);
    white-space: nowrap;
}
/* Chiffres en .num (admin.js), le reste en Public Sans */
.admin-date {
    font-size: 13px;
    color: var(--ink);
    white-space: nowrap;
}
.admin-date--vide { color: var(--muted-ink); }
/* Date et heure exactes sous la duree, en clair (pas de title) */
.admin-date__exacte {
    display: block;
    margin-top: 2px;
    font-size: 12px;
    color: var(--muted-ink);
    white-space: nowrap;
}
/* Sens de « Non enregistree », sous le tableau */
.admin-legende {
    margin: 0;
    padding: 12px 16px;
    border-top: 1px solid var(--line);
    font-size: 12px;
    line-height: 1.4;
    color: var(--muted-ink);
}

.admin-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
}
.admin-actions__role { white-space: nowrap; }
/* Suppression : --danger au survol, comme la deconnexion (charte, A3) */
.admin-actions__suppr:not([aria-disabled="true"]):hover {
    background: var(--danger-soft);
    border-color: var(--danger);
    color: var(--danger);
}

/* Cartes empilees : grille de quatre rangees par compte, en-tetes de
   colonne masques et repris en tete de cellule (data-label). Le badge de
   role garde sa colonne, l'identifiant prend le reste de la largeur. */
@container (max-width: 1019px) {
    .admin-table,
    .admin-table tbody { display: block; }
    .admin-table thead { display: none; }
    .admin-ligne {
        display: grid;
        /* Colonne de droite a largeur fixe (badge OPERATEUR, date de
           creation) : alignee d'une carte a l'autre ; la derniere
           connexion et sa date exacte prennent le reste */
        grid-template-columns: minmax(0, 1fr) minmax(max-content, 100px);
        grid-template-areas:
            "compte    role"
            "connexion creation"
            "etat      etat"
            "actions   actions";
        gap: 12px 16px;
        padding: 16px;
        border-bottom: 1px solid var(--line);
    }
    .admin-ligne:last-child { border-bottom: none; }
    .admin-ligne > .admin-cell,
    .admin-ligne > .admin-cell:first-child,
    .admin-ligne > .admin-cell:last-child {
        display: block;
        min-width: 0;
        padding: 0;
        border: none;
    }
    .admin-table .admin-cell--compte { min-width: 0; }
    .admin-ligne > .admin-cell--compte { grid-area: compte; }
    .admin-ligne > .admin-cell--role { grid-area: role; justify-self: end; }
    .admin-ligne > .admin-cell--connexion { grid-area: connexion; }
    .admin-ligne > .admin-cell--creation { grid-area: creation; }
    .admin-ligne > .admin-cell--actions { grid-area: actions; }
    .admin-ligne > .admin-cell--etat {
        grid-area: etat;
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 12px;
        padding-top: 12px;
        border-top: 1px solid var(--line);
    }
    /* Le badge de role se lit seul : pas de libelle au-dessus */
    .admin-ligne > .admin-cell[data-label]:not(.admin-cell--role)::before {
        content: attr(data-label);
        display: block;
        margin-bottom: 2px;
        font-family: var(--mono);
        font-size: 10px;
        font-weight: 600;
        letter-spacing: 0.06em;
        text-transform: uppercase;
        color: var(--muted-ink);
    }
    .admin-ligne > .admin-cell--etat::before { margin-bottom: 0; }
    .admin-etat__texte { text-align: right; }
    /* Texte d'etat avant l'interrupteur, qui reste contre le bord droit */
    .admin-etat { flex-direction: row-reverse; }
    .admin-actions { justify-content: flex-start; }
    .admin-actions__role { margin-right: auto; }
}
/* Telephone de 320 px : le badge passe sous l'identifiant plutot que de lui
   prendre un tiers de la carte */
@container (max-width: 299px) {
    .admin-ligne {
        grid-template-areas:
            "compte    compte"
            "role      role"
            "connexion creation"
            "etat      etat"
            "actions   actions";
    }
    .admin-ligne > .admin-cell--role { justify-self: start; }
}

/* ---------- Nouveau compte ---------- */
.admin-nouveau { container-type: inline-size; }
.admin-nouveau__corps {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 16px 24px;
    align-items: start;
}
/* Pleine largeur du champ, comme les saisies voisines */
.admin-nouveau__roles { display: flex; }
.admin-nouveau__roles > .seg__btn { flex: 1; }
.admin-nouveau__msg { padding: 0 16px; }
.admin-nouveau__msg > .param-msg:not(:empty) { margin-bottom: 16px; }

@container (max-width: 639px) {
    .admin-nouveau__corps { grid-template-columns: minmax(0, 1fr); }
}

/* Sous 480 px, l'icone plus seule : le titre de la carte garde sa place */
@media (max-width: 480px) {
    .admin-comptes__ajout > span { display: none; }
    .admin-comptes__ajout { width: var(--tap); padding: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .admin-systeme__corps,
    .admin-comptes__corps,
    .admin-robots__corps,
    .admin-ligne,
    .admin-systeme__corps[aria-busy="true"],
    .admin-comptes__corps[aria-busy="true"],
    .admin-robots__corps[aria-busy="true"],
    .admin-ligne[aria-busy="true"] { transition: none; }
}

/* ---------- Robots ----------
   Meme table que Comptes (.admin-table), avec ses propres colonnes. La serie
   est en Plex Mono, les comptes affilies sont des puces retirables. */
.admin-robots__corps {
    container-type: inline-size;
    overflow-x: auto;
}
.admin-table .admin-cell--robot { min-width: 200px; }
.admin-robot__qui {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.admin-robot__serial {
    font-family: var(--mono);
    font-size: 15px;
    font-weight: 600;
    color: var(--ink);
    overflow-wrap: anywhere;
}
.admin-robot__nom {
    font-size: 13px;
    color: var(--ink-soft);
    overflow-wrap: anywhere;
}
.admin-robot__notes {
    max-width: 40ch;
    font-size: 12px;
    line-height: 1.4;
    color: var(--muted-ink);
}
/* Robot en attente : la ligne se remarque, l'action « Approuver » est visible */
.admin-robot.is-attente > .admin-cell { background: color-mix(in srgb, var(--warning) 6%, transparent); }

/* Comptes affilies : puces avec un bouton de retrait */
.admin-affilies {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}
.admin-affilies__vide {
    font-size: 13px;
    color: var(--muted-ink);
}
.admin-affilie {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    padding: 2px 4px 2px 8px;
    background: var(--bg-2);
    border: 1px solid var(--line);
    border-radius: 999px;
    font-size: 13px;
    max-width: 100%;
}
.admin-affilie__nom {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 16ch;
}
.admin-affilie__suppr {
    width: 22px;
    height: 22px;
    flex: none;
    color: var(--ink-soft);
}
.admin-affilie__suppr:hover { color: var(--danger-ink); }
.admin-affilie__suppr > .icon { width: 14px; height: 14px; }

@media (prefers-reduced-motion: reduce) {
    .admin-robots__corps,
    .admin-robots__corps[aria-busy="true"] { transition: none; }
}

/* Cartes empilees (comme Comptes) : la table robots reprend la grille de
   .admin-ligne, avec ses propres zones. Plus specifique que la regle generique
   ci-dessus (memes @container), placee apres elle : elle l'emporte. */
@container (max-width: 1019px) {
    .admin-robot {
        grid-template-columns: minmax(0, 1fr) minmax(max-content, 110px);
        grid-template-areas:
            "robot    statut"
            "activite activite"
            "affilies affilies"
            "actions  actions";
    }
    .admin-robot > .admin-cell--robot { grid-area: robot; }
    .admin-robot > .admin-cell--statut { grid-area: statut; justify-self: end; }
    .admin-robot > .admin-cell--activite { grid-area: activite; }
    .admin-robot > .admin-cell--affilies { grid-area: affilies; }
    .admin-robot > .admin-cell--actions { grid-area: actions; display: flex; }
    /* Le badge de statut se lit seul, pas de libelle au-dessus (comme le role) */
    .admin-robot > .admin-cell--statut::before { content: none; }
    .admin-table .admin-cell--robot { min-width: 0; }
}
@container (max-width: 299px) {
    .admin-robot {
        grid-template-areas:
            "robot    robot"
            "statut   statut"
            "activite activite"
            "affilies affilies"
            "actions  actions";
    }
    .admin-robot > .admin-cell--statut { justify-self: start; }
}
