/**
 * Fiche produit — module Commerce
 *
 * Utilise les composants du back-office : onglets `dev__tabs`, cartes,
 * champs, cases `form-check`. Rien n'est réinventé ici.
 */

.product-panel {
    display: flex;
    flex-direction: column;
    gap: var(--space-lg);
}

/* Contenu principal à gauche, réglages à droite : la description est ce
   qu'on édite le plus, elle mérite la largeur. */
/* Deux colonnes dans un panneau déjà centré : la description reste
   confortable, les réglages tiennent à côté sans être serrés. */
.product-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 300px;
    gap: var(--space-lg);
    align-items: start;
}

.product-side {
    display: flex;
    flex-direction: column;
    gap: var(--space-lg);
}

@media (max-width: 1100px) {
    .product-grid { grid-template-columns: 1fr; }
}

/* Compteur d'onglet : voir d'un coup d'œil ce qui est rempli */
.dev__tab-count {
    min-width: 18px;
    padding: 0 5px;
    border-radius: 999px;
    background: var(--bg-surface-secondary);
    font-size: 11px;
    line-height: 18px;
    text-align: center;
}

.dev__tab.active .dev__tab-count {
    background: var(--primary-500);
    color: #fff;
}

/* Point de traduction remplie : évite d'ouvrir chaque onglet pour voir
   ce qui manque. */
.lang-dot {
    display: inline-block;
    width: 6px;
    height: 6px;
    margin-left: 6px;
    border-radius: 50%;
    background: var(--border-color);
}

.lang-dot--filled { background: var(--success); }

.product-categories {
    display: flex;
    flex-direction: column;
    gap: 2px;
    max-height: 260px;
    overflow-y: auto;
}

/* ============================================================
   IMAGES
   ============================================================ */

.dropzone {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: var(--space-xl);
    border: 2px dashed var(--border-color);
    border-radius: var(--radius-lg);
    background: var(--bg-surface-secondary);
    text-align: center;
    cursor: pointer;
    transition: all var(--transition-fast);
}

.dropzone:hover,
.dropzone--over {
    border-color: var(--primary-500);
    background: rgba(var(--primary-rgb), 0.05);
}

.dropzone i { font-size: 30px; color: var(--primary-500); }
.dropzone p { margin: 0; font-size: var(--font-size-sm); color: var(--text-primary); }
.dropzone span { font-size: var(--font-size-xs); color: var(--text-tertiary); }

.media-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: var(--space-md);
}

/* Message d'état vide dans une grille : sans cette règle, il occupe une
   seule colonne de 150 pixels et se retrouve collé à gauche.
   
   Le centrage lui-même vient de `stats__empty`, qui est déjà en flex —
   il ne manquait que l'étendue. */
.media-grid .stats__empty {
    grid-column: 1 / -1;
}

.media-tile {
    position: relative;
    aspect-ratio: 1;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    overflow: hidden;
    background: var(--bg-surface-secondary);
    cursor: grab;
}

.media-tile img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;

    /* Sans cela, le navigateur glisse l'image elle-même au lieu de la
       vignette, et l'aperçu de déplacement est illisible. */
    pointer-events: none;
}

/* Déplacement en cours : la vignette s'efface pour laisser voir où elle
   va atterrir, les autres glissent doucement. */
.media-tile {
    transition: transform 0.18s ease, opacity 0.18s ease, border-color var(--transition-fast);
}

.media-tile--dragging {
    opacity: 0.35;
    transform: scale(0.96);
    cursor: grabbing;
}


/* La première image est la principale : le signaler évite de chercher
   une case à cocher qui n'existe pas. */
.media-tile__badge {
    position: absolute;
    top: 6px;
    left: 6px;
    padding: 2px 8px;
    border-radius: 999px;
    background: var(--primary-500);
    color: #fff;
    font-size: 10px;
    font-weight: 600;
}

.media-tile__actions {
    position: absolute;
    top: 6px;
    right: 6px;
    display: flex;
    gap: 4px;
    opacity: 0;
    transition: opacity var(--transition-fast);
}

.media-tile:hover .media-tile__actions { opacity: 1; }

.media-tile__btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    border: 0;
    border-radius: var(--radius-sm);
    background: rgba(0, 0, 0, 0.6);
    color: #fff;
    font-size: 12px;
    cursor: pointer;
}

.media-tile__btn:hover { background: rgba(0, 0, 0, 0.8); }
.media-tile__btn--danger:hover { background: var(--danger); }

/* Nom d'origine : l'URL porte un identifiant technique, l'utilisateur a
   besoin de reconnaître son fichier. */
/* Nom du fichier sur le disque, tronqué : c'est un identifiant, pas un
   libellé. En chasse fixe pour qu'on le reconnaisse comme tel et qu'on
   ne cherche pas à y lire un sens. */
.media-tile__name {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    padding: 14px 8px 5px;
    background: linear-gradient(transparent, rgba(0, 0, 0, 0.78));
    color: rgba(255, 255, 255, 0.85);
    font-family: var(--font-mono, ui-monospace, monospace);
    font-size: 10px;
    letter-spacing: -0.01em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ── Variantes ── */

/* Une ligne par déclinaison, comme les champs de personnalisation : le
   détail vit dans une fenêtre, la liste sert à voir la gamme d'un coup. */
.variant-row {
    display: grid;
    grid-template-columns: 22px 44px minmax(0, 1fr) minmax(0, 1.2fr) 60px auto;
    gap: var(--space-md);
    align-items: center;
    padding: var(--space-sm) var(--space-md);
    transition: background var(--transition-fast);
}

.variant-row:hover { background: var(--bg-surface-hover); }

.variant-row__thumb {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: var(--radius-md);
    background: var(--bg-surface-secondary);
    color: var(--text-tertiary);
    overflow: hidden;
}

.variant-row__thumb img { width: 100%; height: 100%; object-fit: cover; }

.variant-row__main { min-width: 0; }

.variant-row__main strong {
    display: block;
    font-size: var(--font-size-sm);
    font-variant-numeric: tabular-nums;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.variant-row__main span {
    font-size: var(--font-size-xs);
    color: var(--text-tertiary);
}

/* Déclinaison en pastilles : « vert » et « L » se distinguent d'un
   regard, là où une chaîne collée demande de lire. */
.variant-row__attrs {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    min-width: 0;
}

.variant-row__chip {
    padding: 2px 10px;
    border-radius: var(--radius-full);
    background: var(--bg-surface-secondary);
    font-size: var(--font-size-xs);
}

/* Compteur d'images propres : il dit qu'une déclinaison a ses visuels
   sans qu'on ouvre la fenêtre. */
.variant-row__media {
    display: flex;
    align-items: center;
    gap: 4px;
    font-size: var(--font-size-xs);
    color: var(--primary-500);
}

.variant-row__actions { display: flex; gap: 2px; }

/* Déclinaison dans la fenêtre : une liste par axe. */
.variant-attrs {
    display: flex;
    gap: var(--space-md);
    flex-wrap: wrap;
}

.variant-attrs__cell { min-width: 150px; flex: 1; }

.variant-attrs__cell .form-label {
    display: block;
    margin-bottom: 5px;
    font-size: var(--font-size-xs);
    color: var(--text-secondary);
}

@media (max-width: 900px) {
    .variant-row {
        grid-template-columns: 22px 44px minmax(0, 1fr) auto;
    }

    .variant-row__attrs,
    .variant-row__media { display: none; }
}

/* ── Champs de personnalisation ── */

/* Une ligne par champ : le détail vit dans une fenêtre, la liste sert à
   voir d'un coup ce que le client devra renseigner. */
.field-row {
    display: grid;
    grid-template-columns: 22px 38px minmax(0, 1fr) 120px minmax(0, 200px) auto auto;
    gap: var(--space-md);
    align-items: center;
    padding: var(--space-sm) var(--space-md);
    transition: background var(--transition-fast);
}

.field-row:hover { background: var(--bg-surface-hover); }

.field-row__icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border-radius: var(--radius-md);
    background: var(--bg-surface-secondary);
    color: var(--primary-500);
}

.field-row__main { min-width: 0; }

.field-row__main strong {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: var(--font-size-sm);
}

/* Mention discrète plutôt qu'une colonne : la plupart des champs ne
   sont pas obligatoires, une colonne serait vide la moitié du temps. */
.field-row__req {
    padding: 1px 7px;
    border-radius: var(--radius-full);
    background: var(--bg-surface-secondary);
    color: var(--text-tertiary);
    font-size: 10px;
    font-weight: 400;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.field-row__help {
    display: block;
    font-size: var(--font-size-xs);
    color: var(--text-tertiary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.field-row__type {
    font-size: var(--font-size-xs);
    color: var(--text-secondary);
}

/* Contraintes résumées : elles disent ce que le champ accepte sans
   qu'on ouvre la fenêtre. */
.field-row__rules {
    font-size: var(--font-size-xs);
    color: var(--text-tertiary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.field-row__extra {
    padding: 2px 8px;
    border-radius: var(--radius-full);
    background: rgba(var(--primary-rgb), 0.1);
    color: var(--primary-500);
    font-size: var(--font-size-xs);
    font-weight: 600;
    white-space: nowrap;
}

.field-row__actions { display: flex; gap: 2px; }

/* ── Fenêtre d'édition d'un champ ── */

.fld-price {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
}

.fld-price select { width: 180px; flex-shrink: 0; }
.fld-price .form-input { width: 140px; }

.fld-percent { display: flex; align-items: center; gap: 6px; }
.fld-percent input { width: 100px; }
.fld-percent span { color: var(--text-tertiary); }

.fld-price [hidden],
.fld-percent[hidden] { display: none; }

/* Extensions groupées par usage : chercher « pdf » dans une liste
   alphabétique de cinquante entrées est plus long. */
.ext-picker {
    margin-top: var(--space-sm);
    padding: var(--space-sm);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    max-height: 220px;
    overflow-y: auto;
}

.ext-group + .ext-group {
    margin-top: var(--space-sm);
    padding-top: var(--space-sm);
    border-top: 1px solid var(--border-color);
}

.ext-group__name {
    display: block;
    margin-bottom: 5px;
    font-size: var(--font-size-xs);
    color: var(--text-tertiary);
}

.ext-group__items { display: flex; flex-wrap: wrap; gap: 4px; }

.ext-chip {
    padding: 3px 10px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-full);
    background: none;
    color: var(--text-secondary);
    font: inherit;
    font-size: var(--font-size-xs);
    cursor: pointer;
    transition: all var(--transition-fast);
}

.ext-chip:hover { border-color: var(--primary-500); }

.ext-chip.active {
    border-color: var(--primary-500);
    background: var(--primary-500);
    color: #fff;
}

@media (max-width: 1100px) {
    .field-row {
        grid-template-columns: 22px 38px minmax(0, 1fr) auto;
        gap: var(--space-sm);
    }

    .field-row__type,
    .field-row__rules { display: none; }
}

/* ── Bloc de référencement ── */

/* Regroupé et encadré : titre, adresse et description forment un
   ensemble cohérent, mêlés aux champs de description ils se
   confondaient avec eux. */
.seo-block {
    margin-top: var(--space-lg);
    padding: var(--space-md);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    background: var(--bg-surface-secondary);
}

.seo-block__head {
    display: flex;
    align-items: flex-start;
    gap: var(--space-sm);
    margin-bottom: var(--space-md);
    padding-bottom: var(--space-sm);
    border-bottom: 1px solid var(--border-color);
}

.seo-block__head > i {
    flex-shrink: 0;
    margin-top: 2px;
    color: var(--text-tertiary);
}

.seo-block__head strong {
    display: block;
    font-size: var(--font-size-sm);
}

.seo-block__head span {
    font-size: var(--font-size-xs);
    color: var(--text-tertiary);
}

/* Dernier champ sans marge : le bloc porte déjà son propre
   remplissage. */
.seo-block .form-group:last-child { margin-bottom: 0; }

/* ── Champ d'adresse ── */

/* Le préfixe et l'identifiant sont affichés, non modifiables : ils sont
   imposés par la route et l'enregistrement. Seul le libellé se saisit. */
/* Hauteur du champ reprise sur l'enveloppe : sans elle, le préfixe et
   la saisie ne partagent pas la même ligne de base. */
.slug-field {
    display: flex;
    align-items: stretch;
    height: 42px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-input);
    overflow: hidden;
}

.slug-field:focus-within {
    border-color: var(--primary-500);
    box-shadow: 0 0 0 3px rgba(var(--primary-rgb), 0.12);
}

/* Le préfixe est affiché, non modifiable : il est imposé par la route.
   Seul le libellé se saisit.
   
   Aligné en flex sur toute la hauteur, avec le même remplissage que le
   champ : posé en ligne, son texte flottait au-dessus de la saisie. */
.slug-field__prefix {
    display: flex;
    align-items: center;
    flex-shrink: 0;

    /* Même remplissage horizontal que .form-input : une valeur
       différente décalerait le préfixe par rapport au texte saisi. */
    padding-left: 14px;

    /* Reprise du champ : une taille différente désalignerait les deux
       textes. La hauteur de ligne est laissée à l'héritage, comme pour
       le champ — la forcer ici décalerait justement le préfixe. */
    font-size: var(--font-size-sm);

    color: var(--text-tertiary);
    white-space: nowrap;
    user-select: none;
}

/* Le champ perd sa bordure : l'enveloppe la porte, sinon deux cadres se
   superposent. */
.slug-field .form-input {
    flex: 1;
    min-width: 0;
    height: 100%;
    border: 0;
    border-radius: 0;
    background: transparent;

    /* Collé au préfixe : l'adresse se lit d'un trait, sans espace au
       milieu qui laisserait croire à deux champs. */
    padding-left: 0;
    padding-right: 14px;
}

.slug-field .form-input:focus { box-shadow: none; }

/* Zones de texte à hauteur fixe : redimensionnées à la main, elles
   désalignent la colonne et l'ajustement se perd au rechargement. */
.form-textarea--fixed { resize: none; }

/* ── Images de variante ── */

/* La variante reprend les images du produit : chaque vignette est un
   emplacement cliquable, remplaçable individuellement. */
.variant-media {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-xs);
}

.variant-media__slot {
    position: relative;
    width: 56px;
    height: 56px;
    padding: 0;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    background: var(--bg-surface-secondary);
    overflow: hidden;
    cursor: pointer;
    transition: border-color var(--transition-fast);
}

.variant-media__slot:hover { border-color: var(--primary-500); }

.variant-media__slot img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Image remplacée : contour violet et crayon, pour distinguer d'un
   coup d'œil ce qui a été personnalisé de ce qui est hérité. */
.variant-media__slot--custom { border-color: var(--primary-500); border-width: 2px; }

.variant-media__slot--custom i {
    position: absolute;
    right: 3px;
    bottom: 3px;
    width: 15px;
    height: 15px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--primary-500);
    color: #fff;
    font-size: 8px;
}

/* ── Sélecteur d'images ── */

.media-picker {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
    gap: var(--space-sm);
    margin-top: var(--space-md);
    max-height: 340px;
    overflow-y: auto;
}

.media-picker__item {
    position: relative;
    aspect-ratio: 1;
    border: 2px solid transparent;
    border-radius: var(--radius-sm);
    overflow: hidden;
    cursor: pointer;
}

.media-picker__item img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Le bouton natif est masqué : la bordure et la pastille suffisent à
   dire ce qui est retenu, et elles se voient sur une vignette. */
.media-picker__item input { position: absolute; opacity: 0; pointer-events: none; }

.media-picker__item--on { border-color: var(--primary-500); }

.media-picker__item--on::after {
    content: "\f26e";
    font-family: "bootstrap-icons";
    position: absolute;
    top: 4px;
    right: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: var(--primary-500);
    color: #fff;
    font-size: 11px;
}


/* ── Sélecteur d'images ── */

.media-picker {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
    gap: var(--space-sm);
    margin-top: var(--space-md);
    max-height: 340px;
    overflow-y: auto;
}

.media-picker__item {
    position: relative;
    aspect-ratio: 1;
    border: 2px solid transparent;
    border-radius: var(--radius-sm);
    overflow: hidden;
    cursor: pointer;
}

.media-picker__item img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* La case native est masquée : la bordure et la pastille suffisent à
   dire ce qui est retenu, et elles se voient sur une vignette. */
.media-picker__item input { position: absolute; opacity: 0; pointer-events: none; }

.media-picker__item--on { border-color: var(--primary-500); }

.media-picker__item--on::after {
    content: "\f26e";
    font-family: "bootstrap-icons";
    position: absolute;
    top: 4px;
    right: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: var(--primary-500);
    color: #fff;
    font-size: 11px;
}

/* ── Tableau des prix ── */

/* Une seule ligne par élément, en grille.
   
   Référence et tarification à gauche, réglages à droite, séparés par
   une colonne souple : le vide au milieu vaut mieux qu'un sélecteur
   étiré sur toute la largeur disponible.

   Les champs indisponibles sont DÉSACTIVÉS et non masqués : les retirer
   décalerait les colonnes des lignes voisines. */
.price-head,
.price-row {
    display: grid;
    /* Colonnes fixes plutôt qu'`auto` : une colonne au contenu prenait
       la largeur du plus long libellé sur TOUTES les lignes, poussant
       les suivantes hors de la vue. */
    grid-template-columns:
        minmax(140px, 1.4fr)   /* référence */
        150px                  /* tarification */
        110px                  /* prix */
        124px                  /* stock */
        80px                   /* alerte */
        112px                  /* rupture */
        90px;                  /* poids */
    gap: var(--space-sm);
    align-items: center;
}

/* Produit numérique : ni stock ni poids. */
.price-row:not(:has([data-p="stock"])),
.price-head:not(:has(span:nth-child(4))) {
    grid-template-columns: minmax(140px, 1.4fr) 150px 110px;
}

.price-head {
    padding: 0 var(--space-sm) var(--space-xs);
    font-size: var(--font-size-xs);
    color: var(--text-tertiary);
}

.price-row {
    padding: var(--space-sm);
}

.price-row:hover { background: var(--bg-surface-hover); }

/* Le produit de base se distingue : c'est la référence, pas une
   déclinaison parmi d'autres. */
.price-row--base {
    background: var(--bg-surface-secondary);
    margin-bottom: var(--space-sm);
}

.price-row--base:hover { background: var(--bg-surface-secondary); }

/* Référence en tête, libellé dessous : c'est elle qui identifie la
   ligne dans un bon de commande ou un inventaire. */
.price-ref {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
}

.price-ref strong {
    font-size: var(--font-size-sm);
    font-variant-numeric: tabular-nums;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.price-ref span {
    font-size: var(--font-size-xs);
    color: var(--text-tertiary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Sélecteur de tarification à son contenu, pas étiré : il n'a pas
   besoin de plus que ses trois libellés. */
.price-mode { width: 100%; }

/* Le produit de base porte une étiquette, pas un choix : sa forme doit
   se distinguer du sélecteur voisin. */
.price-base-tag {
    display: inline-flex;
    align-items: center;
    height: 26px;
    padding: 0 12px;
    border-radius: 999px;
    background: var(--primary-500);
    color: #fff;
    font-size: var(--font-size-xs);
    white-space: nowrap;
    justify-content: center;

    /* Ni étirée ni débordante : la colonne est fixe, la pastille s'y
       centre. */
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Hauteur identique pour TOUS les champs : c'est ce qui donne
   l'alignement d'une ligne à l'autre. */
.price-row input,
.price-row select,
.price-row .cselect {
    width: 100%;
    height: 34px;
}

.price-row input[type="number"],
.price-row input.js-money { text-align: right; }

/* Champs désactivés : visibles mais manifestement inactifs. Les
   masquer romprait l'alignement des colonnes. */
.price-row input:disabled,
.price-row select:disabled,
.price-row .cselect--disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

/* Colonne de rupture : un sélecteur quand le stock est suivi, une
   mention grisée sinon.
   
   « Sans objet » plutôt qu'un sélecteur grisé : on se demanderait
   quelle valeur il porte, alors que la question ne se pose pas. */
.price-policy {
    display: flex;
    align-items: center;
    min-width: 0;
}

.price-policy select { width: 100%; }

.price-policy--off {
    justify-content: center;
    height: 34px;
    border: 1px dashed var(--border-color);
    border-radius: var(--radius-md);
}

.price-policy--off span {
    font-size: var(--font-size-xs);
    color: var(--text-tertiary);
    font-style: italic;
}

/* Interrupteur de stock collé à son champ : ils forment un ensemble. */
.price-stock-cell {
    display: flex;
    align-items: center;
    gap: 4px;
    min-width: 0;
}

.price-stock-cell input { flex: 1; min-width: 0; }

.price-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 34px;
    flex-shrink: 0;
    color: var(--text-tertiary);
    cursor: pointer;
    transition: color var(--transition-fast);
}

.price-toggle input { position: absolute; opacity: 0; pointer-events: none; }
.price-toggle:has(input:checked) { color: var(--primary-500); }

/* Unité collée au champ : « 250 g » se lit d'un trait. */
.price-unit-cell {
    display: flex;
    align-items: center;
    gap: 4px;
    min-width: 0;
}

.price-unit-cell input { flex: 1; min-width: 0; }

.price-unit-cell span {
    flex-shrink: 0;
    font-size: var(--font-size-xs);
    color: var(--text-tertiary);
}

/* Sous 1300 pixels, la ligne défile plutôt que de se replier : neuf
   colonnes empilées perdraient l'alignement qui fait tout l'intérêt. */
@media (max-width: 1300px) {
    #pricing-table {
        overflow-x: auto;
        padding-bottom: var(--space-sm);
    }

    .price-head,
    .price-row { min-width: 860px; }
}

/* ── Champs ── */

/* Grille à colonnes régulières : les champs s'alignent d'une ligne à
   l'autre, ce qu'un simple `flex` ne garantit pas. */
.price-row__grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: var(--space-md);
    align-items: end;
}


/* Champ de suffixe de référence : le préfixe du produit est affiché,
   non modifiable — il vient de la fiche produit. */
.sku-field {
    display: flex;
    align-items: stretch;
    height: 42px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-input);
    overflow: hidden;
}

.sku-field:focus-within {
    border-color: var(--primary-500);
    box-shadow: 0 0 0 3px rgba(var(--primary-rgb), 0.12);
}

/* Majuscules imposées à l'affichage aussi : une référence chargée
   depuis la base avant cette règle resterait en minuscules jusqu'au
   prochain enregistrement. */
.sku-field > span,
.sku-field input,
#p-sku,
[data-v="sku"] { text-transform: uppercase; }

.sku-field > span {
    display: flex;
    align-items: center;
    flex-shrink: 0;
    padding-left: 14px;
    font-size: var(--font-size-sm);
    font-variant-numeric: tabular-nums;
    color: var(--text-tertiary);
    white-space: nowrap;
    user-select: none;
}

.sku-field .form-input {
    flex: 1;
    min-width: 0;
    height: 100%;
    border: 0;
    border-radius: 0;
    background: transparent;
    padding-left: 0;
}

.sku-field .form-input:focus { box-shadow: none; }

/* ── Axes de déclinaison ── */

/* Un axe par bloc : nom à gauche, valeurs en pastilles dessous. Les
   saisir dans chaque variante imposait une syntaxe à retenir et
   laissait passer les fautes de frappe. */
.axis {
    padding: var(--space-md);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
}

.axis + .axis { margin-top: var(--space-sm); }

.axis__head {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    margin-bottom: var(--space-sm);
}

/* Poignée de déplacement : sans repère visible, rien ne dit qu'un axe
   se réordonne. */
.axis__grip {
    display: flex;
    align-items: center;
    flex-shrink: 0;
    color: var(--text-tertiary);
    cursor: grab;
}

.axis__grip:active { cursor: grabbing; }

/* Élément en cours de déplacement : réduit à une empreinte. Il se
   déplace réellement dans la liste pendant le geste, on voit donc où il
   se posera sans avoir besoin d'un repère séparé. */
.axis.is-dragging,
.axis__value.is-dragging {
    opacity: 0.3;
    background: var(--bg-surface-secondary);
}

/* Transition sur la position : les voisins glissent au lieu de sauter
   quand l'élément s'insère entre eux. */
.axis,
.axis__value { transition: transform var(--transition-fast); }

/* Suspendue pendant le glisser : animer l'élément qu'on tient le fait
   traîner derrière le curseur. */
.axis.is-dragging,
.axis__value.is-dragging { transition: none; }

.axis__name { width: 220px; flex-shrink: 0; }

.axis__values {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
}

/* Valeur en pastille : compacte, et sa croix dit qu'elle se retire. */
.axis__value {
    cursor: grab;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    height: 28px;
    padding: 0 6px 0 12px;
    border-radius: var(--radius-full);
    background: var(--bg-surface-secondary);
    font-size: var(--font-size-sm);
}

.axis__value button {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    border: 0;
    border-radius: 50%;
    background: none;
    color: var(--text-tertiary);
    font-size: 10px;
    cursor: pointer;
    transition: all var(--transition-fast);
}

.axis__value button:hover { background: var(--danger); color: #fff; }

/* Champ d'ajout sans bordure : il prolonge la rangée de pastilles au
   lieu de former un second bloc. */
.axis__add {
    flex: 1;
    min-width: 140px;
    height: 28px;
    padding: 0 8px;
    border: 0;
    border-bottom: 1px dashed var(--border-color);
    background: none;
    color: var(--text-primary);
    font: inherit;
    font-size: var(--font-size-sm);
    outline: none;
}

.axis__add:focus { border-bottom-color: var(--primary-500); }

.axes-actions {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    margin-top: var(--space-md);
    padding-top: var(--space-md);
    border-top: 1px solid var(--border-color);
}

/* Nombre de combinaisons annoncé avant de générer : sans lui, on en
   crée cent sans s'en rendre compte. */
.axes-count {
    font-size: var(--font-size-xs);
    color: var(--text-tertiary);
}

/* Déclinaison d'une variante : une liste par axe déclaré. */
.variant-attrs {
    display: flex;
    gap: var(--space-sm);
    flex-wrap: wrap;
}

.variant-attrs select { min-width: 130px; }

/* ── Remplacement d'image par variante ── */

/* Avant et après côte à côte : on voit ce qu'on remplace et par quoi,
   ce qu'une liste de vignettes ne dit pas. */
.override {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    gap: var(--space-lg);
    align-items: center;
}

.override__label {
    display: block;
    margin-bottom: var(--space-xs);
    font-size: var(--font-size-xs);
    color: var(--text-tertiary);
}

.override__current img {
    width: 100%;
    aspect-ratio: 1;
    object-fit: cover;
    border-radius: var(--radius-md);
    border: 1px solid var(--border-color);
}

.override__arrow {
    color: var(--text-tertiary);
    font-size: var(--font-size-lg);
}

/* Zone de dépôt en pointillé : elle dit qu'on peut y déposer un fichier
   sans qu'il faille l'écrire. */
.override__drop {
    position: relative;
    aspect-ratio: 1;
    border: 2px dashed var(--border-color);
    border-radius: var(--radius-md);
    cursor: pointer;
    overflow: hidden;
    transition: border-color var(--transition-fast), background var(--transition-fast);
}

.override__drop:hover,
.override__drop.is-over {
    border-color: var(--primary-500);
    background: rgba(var(--primary-rgb), 0.04);
}

.override__empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    height: 100%;
    padding: var(--space-md);
    text-align: center;
}

.override__empty i { font-size: var(--font-size-2xl); color: var(--text-tertiary); }
.override__empty strong { font-size: var(--font-size-sm); }
.override__empty span { font-size: var(--font-size-xs); color: var(--text-tertiary); }

.override__preview { position: absolute; inset: 0; }

.override__preview img { width: 100%; height: 100%; object-fit: cover; display: block; }

.override__clear {
    position: absolute;
    top: 6px;
    right: 6px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    border: 0;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.6);
    color: #fff;
    font-size: 11px;
    cursor: pointer;
}

.override__clear:hover { background: var(--danger); }

/* Bibliothèque repliée : elle sert à corriger un choix, pas au geste
   courant qui est d'envoyer une photo. */
.override__library {
    margin-top: var(--space-lg);
    padding-top: var(--space-md);
    border-top: 1px solid var(--border-color);
}

.override__library summary {
    font-size: var(--font-size-sm);
    color: var(--text-secondary);
    cursor: pointer;
}

.override__library summary:hover { color: var(--text-primary); }

@media (max-width: 700px) {
    .override { grid-template-columns: 1fr; }
    .override__arrow { transform: rotate(90deg); justify-self: center; }
}

/* Guide de distinction : la confusion entre déclinaison et
   personnalisation est la plus fréquente, et elle coûte cher — un
   catalogue mal structuré se refait entièrement. */
.perso-guide {
    margin: var(--space-sm) 0 0;
    padding-left: var(--space-md);
}

.perso-guide li + li { margin-top: 4px; }

/* ── Lignes : repères communs ── */

/* Alternance : sur une liste longue, l'œil perd la ligne qu'il suit en
   passant d'une colonne à l'autre.
   
   Teinte neutre plutôt que colorée : à 2,5 % de violet, l'écart était
   invisible sur un fond déjà clair. Un gris à 4 % se distingue sans
   attirer l'attention. */
.variant-row:nth-child(even),
.field-row:nth-child(even),
.price-row:nth-child(even) {
    background: rgba(127, 127, 127, 0.06);
}

/* Séparateur discret : l'alternance seule ne suffit pas quand deux
   lignes voisines sont peu remplies. */
.variant-row + .variant-row,
.field-row + .field-row,
.price-row + .price-row {
    border-top: 1px solid var(--border-color);
    margin-top: 0;
}

/* Le survol reste plus marqué que l'alternance, sinon il ne se
   distinguerait plus une ligne sur deux. */
.variant-row:hover,
.field-row:hover,
.price-row:hover { background: var(--bg-surface-hover); }

/* Ligne cochée : sur une liste longue, on perd de vue ce qu'on a retenu
   en haut une fois descendu. */
.variant-row.is-selected,
.field-row.is-selected {
    background: rgba(var(--primary-rgb), 0.09);
    box-shadow: inset 2px 0 0 var(--primary-500);
}

.row-check {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    flex-shrink: 0;
    cursor: pointer;
}

.row-check input { cursor: pointer; }

/* ── Actions groupées ── */

/* Masquée tant que rien n'est coché : présente en permanence, elle
   occuperait de la place pour une action rarement utilisée. */
/* Toujours visible : sa case sert à tout cocher, la masquer la rendrait
   inatteignable. Seule l'action apparaît avec la sélection. */
.bulk-bar {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-sm) var(--space-md);
    border-bottom: 1px solid var(--border-color);
    transition: background var(--transition-fast);
}

.bulk-bar.has-selection { background: rgba(var(--primary-rgb), 0.08); }

.bulk-bar__count {
    flex: 1;
    font-size: var(--font-size-sm);
    color: var(--text-tertiary);
}

.bulk-bar.has-selection .bulk-bar__count {
    color: var(--primary-600);
    font-weight: 600;
}

/* Action masquée hors sélection : proposer « supprimer » quand rien
   n'est coché laisse se demander ce qui serait supprimé. */
.bulk-bar__action { display: none; }
.bulk-bar.has-selection .bulk-bar__action { display: inline-flex; }

/* ── Navigation dans les fenêtres ── */

/* Corriger dix déclinaisons demandait dix allers-retours entre la liste
   et la fenêtre. */
.modal-nav {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    margin-left: var(--space-md);
    padding: 2px;
    border-radius: var(--radius-full);
    background: var(--bg-surface-secondary);
    vertical-align: middle;
}

.modal-nav__btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    border: 0;
    border-radius: 50%;
    background: none;
    color: var(--text-secondary);
    font-size: 12px;
    cursor: pointer;
    transition: all var(--transition-fast);
}

.modal-nav__btn:hover:not(:disabled) {
    background: var(--primary-500);
    color: #fff;
}

.modal-nav__btn:disabled { opacity: 0.3; cursor: default; }

.modal-nav__pos {
    padding: 0 var(--space-xs);
    font-size: var(--font-size-xs);
    color: var(--text-tertiary);
    font-variant-numeric: tabular-nums;
}

/* ── Extensions ── */

/* Une carte par extension : son état d'activation en tête, ses réglages
   dessous. Les réglages sont masqués tant qu'elle est éteinte — les
   montrer laisserait croire qu'ils s'appliquent. */
.ext-card {
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    transition: border-color var(--transition-fast);
}

.ext-card + .ext-card { margin-top: var(--space-sm); }

.ext-card.is-on { border-color: var(--primary-500); }

.ext-card__head {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    padding: var(--space-md);
}

.ext-card__icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    flex-shrink: 0;
    border-radius: var(--radius-md);
    background: var(--bg-surface-secondary);
    color: var(--text-tertiary);
    font-size: var(--font-size-lg);
}

.ext-card.is-on .ext-card__icon {
    background: rgba(var(--primary-rgb), 0.1);
    color: var(--primary-500);
}

.ext-card__main { flex: 1; min-width: 0; }

.ext-card__main strong {
    display: block;
    font-size: var(--font-size-sm);
}

.ext-card__main span {
    font-size: var(--font-size-xs);
    color: var(--text-tertiary);
}

.ext-card__body {
    padding: var(--space-md);
    border-top: 1px solid var(--border-color);
}

.ext-card__body[hidden] { display: none; }

.ext-card__color {
    width: 60px;
    height: 34px;
    padding: 2px;
    cursor: pointer;
}

/* Avertissement quand un réglage ne peut pas être satisfait : dire ce
   qui manque évite de chercher pourquoi la liste est vide. */
.ext-card__warn {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    margin: 0;
    padding: var(--space-sm);
    border-radius: var(--radius-sm);
    background: rgba(217, 119, 6, 0.08);
    color: var(--warning-dark, #b45309);
    font-size: var(--font-size-xs);
}
