/* NARVALIO — ivory on deep water. System fonts only (§7: 0 web fonts).
 *
 * Red (--alert) is reserved for errors, the expiry warning and destruction —
 * never decoration, and jamais pour un geste anodin. UNE exception à la règle
 * du « pas de couleur », ajoutée le 05/09/2026 : les deux boutons d'atelier de
 * la console d'administration (--go, --hold). Là non plus la couleur ne décore
 * pas — elle distingue le geste qui rend trente cartes activables de celui qui
 * exporte un fichier, dans une rangée de six boutons autrement identiques.
 * Rien de tout cela ne paraît sur le site public. */
:root {
  --abyss: #071726;   /* page background */
  --ink: #0E2438;     /* cards / panels */
  --line: #1B3348;
  --tusk: #EDE7DA;    /* primary text (ivory) */
  --tusk-dim: #9AA7B2;/* muted text */
  --glacier: #7FB0CC; /* accent, links, focus */
  --alert: #C2543E;   /* errors + expiry only */
  --go:    #4E9E7A;   /* atelier : le geste qui avance */
  --hold:  #C98A3E;   /* atelier : le geste irréversible */
  --radius: 12px;
  --mono: ui-monospace, "Cascadia Code", "SFMono-Regular", Consolas, monospace;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  font-weight: 300;
  background: var(--abyss);
  color: var(--tusk);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

.page-centered {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

/* L'attribut hidden doit gagner, TOUJOURS.
 *
 * La règle du navigateur — [hidden] { display: none } — a la spécificité d'un
 * simple sélecteur d'attribut. N'importe quelle classe qui pose un display la
 * bat : `.btn { display: inline-block }` suffisait à rendre visible un bouton
 * pourtant marqué hidden. Le parcours en trois étapes affichait ainsi son
 * bouton « Envoyer » et son « Précédent » dès la première étape.
 *
 * La feuille portait déjà un correctif ponctuel pour ce défaut, `.modal[hidden]`
 * plus bas : signe que le piège avait été rencontré une fois et traité au cas
 * par cas. Cette règle-ci le ferme pour de bon.
 *
 * !important est ici légitime : « hidden » n'est pas une préférence de mise en
 * page, c'est une affirmation sur l'état du document. Rien ne doit la contredire.
 */
[hidden] { display: none !important; }

.card {
  width: 100%;
  max-width: 460px;
  background: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 30px;
}
.card-wide { max-width: 680px; }
/* L'ADMIN seul. 680 px suffisent à un formulaire de connexion ou à une
   candidature, pas à un tableau de bord dont les lignes portent quatre
   champs : la quatrième colonne passait à la ligne suivante, et la case
   « En vente » se retrouvait sous les champs au lieu d'être à côté.
   Classe distincte, et non un élargissement de card-wide : la page
   d'inscription et l'écran de connexion, eux, doivent rester étroits —
   une ligne de texte trop large se lit mal. */
.card-admin { max-width: 1080px; }

/* Logotype: monospace, echoing the 16-char identifier the product is built on. */
.brand {
  margin: 0 0 12px;
  font-family: var(--mono);
  font-weight: 500;
  font-size: 1.4rem;
  letter-spacing: 0.03em;
  color: var(--tusk);
}
.wordmark {
  display: flex;
  align-items: center;
  gap: 14px;
  letter-spacing: 0.26em;
  text-indent: 0.26em;
}
.wordmark .logo { width: 1.5em; height: 1.5em; color: var(--tusk); flex: none; }

.lede { font-size: 1.05rem; color: var(--tusk); }
.note { color: var(--tusk); }
.note-success { color: var(--glacier); }
.fineprint { color: var(--tusk-dim); font-size: 0.85rem; }
.status { min-height: 1.2em; color: var(--alert); font-size: 0.9rem; }

.actions { display: flex; gap: 12px; margin: 18px 0 6px; flex-wrap: wrap; }

.btn {
  display: inline-block;
  padding: 11px 18px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: transparent;
  color: var(--tusk);
  font: inherit;
  font-weight: 400;
  text-decoration: none;
  cursor: pointer;
}
.btn:hover { border-color: var(--glacier); }
.btn-primary { background: var(--glacier); border-color: var(--glacier); color: var(--abyss); font-weight: 500; }
.btn-primary:hover { filter: brightness(1.06); }
.btn-small { padding: 6px 10px; font-size: 0.85rem; }
.btn-danger { color: var(--alert); border-color: var(--alert); }
.btn-confirm { background: var(--alert); border-color: var(--alert); color: #fff; font-weight: 500; }
.btn[disabled] { opacity: 0.45; cursor: not-allowed; }

/* LES DEUX GESTES QUI AVANCENT, en pleine couleur.
 *
 * La console d'administration porte une rangée de six boutons par lot, tous
 * gris. L'œil ne distinguait plus « Exporter » de « Marquer livré », qui rend
 * trente cartes activables. La couleur ne décore pas ici : elle dit le SENS du
 * geste, et le rend reconnaissable sans lire.
 *
 *   vert   — avancer d'un cran   (marquer imprimé)
 *   orange — le pas irréversible (marquer livré : les cartes s'activent)
 *
 * Et RIEN pour revenir en arrière. Le retour a d'abord été rouge : trois
 * rouges se suivaient alors dans la même rangée — « Remettre à imprimer »,
 * « Annuler le lot », « Supprimer » — pour deux sens, défaire une marque sans
 * conséquence et détruire des cartes. Une couleur qui dit deux choses ne dit
 * plus rien. Le rouge est donc rendu à ce qu'il désignait déjà : la
 * destruction, en contour (`.btn-danger`).
 *
 * Fond plein et non contour : à cette taille, un contour se confond avec la
 * bordure des boutons voisins.
 *
 * Texte sombre sur vert et sur orange — dans les deux cas au-dessus de 5:1, la
 * console se lit de près et longtemps.
 */
.btn-go   { background: var(--go);   border-color: var(--go);   color: var(--abyss); font-weight: 500; }
.btn-step { background: var(--hold); border-color: var(--hold); color: var(--abyss); font-weight: 500; }
.btn-go:hover, .btn-step:hover { filter: brightness(1.08); }

/* Confirmation popup (in-theme, replaces the native browser alert) */
.modal {
  position: fixed; inset: 0; z-index: 100;
  display: flex; align-items: center; justify-content: center; padding: 24px;
  background: rgba(6, 18, 30, 0.72);
}
.modal[hidden] { display: none; }
.modal-box {
  width: 100%; max-width: 420px;
  background: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 24px;
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.45);
}
.modal-actions { display: flex; gap: 12px; justify-content: flex-end; margin-top: 20px; }
.admin-actions { margin: 12px 0 6px; }
.vault-actions { margin: 2px 0 14px; }

.field { display: block; margin: 0 0 16px; }
.field span { display: block; margin-bottom: 6px; color: var(--tusk-dim); font-size: 0.9rem; }
/* Tout contrôle posé dans un .field, y compris un <select> : sans cette
   troisième ligne, un menu déroulant garde le blanc natif du navigateur au
   milieu de champs sombres. Régler le cas ici plutôt que panneau par panneau
   évite que le prochain écran reparte avec le même défaut. */
.field input,
.field textarea,
.field select {
  width: 100%;
  padding: 11px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #0A1F31;
  color: var(--tusk);
  font: inherit;
}
.field textarea { min-height: 160px; resize: vertical; }
/* Un <select>, un input[type=number] et un input[type=date] n'ont pas la même
   hauteur naturelle (47 / 50 / 52 px mesurés) : côte à côte dans une rangée les
   champs paraissent décalés même quand la grille les aligne. On leur impose la
   même hauteur, en em pour qu'elle suive la taille du texte plutôt qu'un chiffre
   figé. Le <textarea> en est exclu : il garde sa hauteur libre. */
.field input,
.field select {
  line-height: 1.5;
  height: calc(1.5em + 24px);   /* texte 1.5em + padding 22px + bordure 2px */
}
.field input:focus,
.field textarea:focus,
.field select:focus { outline: 2px solid var(--glacier); outline-offset: 1px; }
/* Narrow field for the anti-robot answer. */
.field-short input { max-width: 140px; }
/* Honeypot: off-screen rather than display:none, which some bots skip. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* Pre-payment obligations (§4/§8) */
.warnings-title { margin-bottom: 6px; }
.warnings { margin: 0 0 18px; padding-left: 20px; color: var(--tusk-dim); font-size: 0.88rem; }
.warnings li { margin: 4px 0; }
/* Formulaire d'inscription revendeur : deux colonnes dans chaque étape.
 *
 * Dix-neuf champs sur une seule colonne donnaient trois étapes de hauteurs
 * très différentes, et la hauteur figée — nécessaire pour que la carte ne
 * saute pas d'une étape à l'autre — laissait alors un grand vide sous la
 * première. Deux colonnes rapprochent les trois étapes et réduisent ce vide
 * d'autant.
 *
 * Le seuil de 640 px est celui en dessous duquel deux champs côte à côte
 * deviennent illisibles : sur téléphone, la grille ne s'applique pas et le
 * formulaire reste en une colonne, ce qui est la bonne mise en page là.
 *
 * align-content: start empêche la grille d'étirer les lignes pour remplir la
 * hauteur minimale imposée au formulaire : les champs restent groupés en haut
 * plutôt que de flotter, espacés, dans toute la carte.
 */
/* La hauteur figée laisse forcément du vide sous les étapes courtes : cinq
 * champs ne rempliront jamais la place qu'il en faut sept plus deux zones de
 * texte. Ce vide ne peut pas disparaître — mais il peut être placé.
 *
 * Le formulaire devient une colonne souple et le bloc des étapes en absorbe le
 * surplus. Les boutons se retrouvent donc TOUJOURS au bas de la zone, au même
 * endroit d'une étape à l'autre. Le vide passe entre les champs et les
 * boutons, là où il se lit comme une respiration, au lieu de traîner sous un
 * bouton isolé au milieu de la carte.
 */
.signup form { display: flex; flex-direction: column; }
.signup .etapes { flex: 1 0 auto; }

.etape-nav { display: flex; align-items: center; gap: 12px; margin: 0 0 6px; }

/* Le compteur d'étapes se pose à côté du titre de l'étape.
 *
 * Seul sur sa ligne, il coûtait une rangée entière — sur chaque étape, pour
 * trois caractères. À droite des boutons, il pendait sans rien à quoi se
 * rattacher. Au titre, il précise ce que le titre annonce déjà.
 *
 * Le script l'y déplace ; la règle ci-dessous le rend discret pour qu'il
 * accompagne le titre sans lui disputer la place. */
.etape h2 #etape-compteur {
  margin-left: 12px;
  /* En em, non en rem : la taille suit celle du titre. Le .fineprint dont il
     hérite est fixé en rem, ce qui le laissait minuscule à côté d'un titre
     qui, lui, grandit avec le réglage de police du lecteur. */
  font-size: 0.72em;
  font-weight: 400;
  vertical-align: 0.12em;
}

/* La zone d'informations de livraison n'a pas besoin des cent soixante pixels
 * que les .field textarea prennent par défaut : on y écrit « ouvert 9h-18h,
 * livrer côté cour », pas un texte. Réduite, elle rapproche la seconde étape
 * de la première — et deux étapes de même hauteur ne sautent pas l'une sur
 * l'autre. Elle reste redimensionnable à la main si quelqu'un a plus à dire. */
.signup .etape textarea { min-height: 96px; }

/* Le passage d'une étape à l'autre glisse, il ne saute pas.
 *
 * Chaque étape a SA hauteur — imposer à toutes celle de la plus haute laissait
 * un demi-écran de vide sous la première, qui n'a que trois rangées. Mais une
 * hauteur qui change d'un coup fait se déplacer les boutons sous le curseur au
 * moment même du clic. La transition résout les deux : rien n'est vide, rien
 * ne sursaute.
 *
 * Deux cent vingt millisecondes : assez pour que l'œil suive, assez peu pour
 * ne pas retenir la main.
 */
.signup form { transition: min-height 220ms ease; }

/* Qui a demandé moins d'animation n'en reçoit aucune. Le mouvement n'est ici
   qu'un confort ; pour une personne sujette au mal des transports ou aux
   troubles vestibulaires, il peut être tout autre chose. */
@media (prefers-reduced-motion: reduce) {
  .signup form { transition: none; }
}

@media (min-width: 640px) {
  .etape {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 18px;
    align-content: start;
  }
  /* Ce qui traverse les deux colonnes : titres, textes d'aide, zones de texte
     libre et la case de consentement. Une zone de texte à demi-largeur est
     inutilisable, et un titre en colonne de gauche ne titre plus rien. */
  /* Ce qui traverse les deux colonnes : titres, textes d'aide, zones de texte
     libre et la case de consentement.

     La case a été essayée sur une demi-largeur, à côté de la question
     anti-robot : sa phrase passait alors sur trois lignes dans un encadré
     étroit, et l'ensemble se lisait mal. Une phrase de consentement doit se
     lire d'un trait — c'est le seul texte de ce formulaire que la personne
     s'engage à avoir lu. */
  .etape > h1,
  .etape > h2,
  .etape > h3,
  .etape > p,
  .etape > .field-wide,
  .etape > .waiver { grid-column: 1 / -1; }

  /* Code postal, ville et pays sur UNE rangée, comme on les écrit sur une
   * enveloppe.
   *
   * Chacun dans sa colonne de la grille à deux, le code postal laissait les
   * deux tiers de la sienne vides et le pays se retrouvait seul sur la rangée
   * suivante. Groupés dans une seule case, ils devenaient trop étroits — la
   * ville tombait à cent quarante-huit pixels. Ils prennent donc la rangée
   * entière et se la partagent selon leur besoin réel.
   *
   * « auto 1fr 1fr » : au code postal la largeur qu'il demande, à la ville et
   * au pays le reste à parts égales — et non un tiers chacun, qui donnerait
   * au code postal trois fois la place qu'il utilise. */
  .etape > .field-paire {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: auto 1fr 1fr;
    column-gap: 18px;
    align-items: start;
  }
}

.waiver {
  display: flex; gap: 10px; align-items: flex-start;
  margin: 0 0 16px; padding: 14px;
  border: 1px solid var(--line); border-radius: 8px;
  font-size: 0.88rem; color: var(--tusk);
}
.waiver input { margin-top: 3px; flex: none; accent-color: var(--glacier); }
.cta-subtitle { margin: 2px 0 0; }

.uid-display {
  font-family: var(--mono);
  font-size: 1.25rem;
  letter-spacing: 0.14em;
  background: #0A1F31;
  border: 1px dashed var(--line);
  border-radius: 8px;
  padding: 14px;
  text-align: center;
  word-break: break-word;
  color: var(--tusk);
  width: 100%;
}

.vault-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; }

.uploader { margin: 18px 0; padding: 16px; border: 1px dashed var(--line); border-radius: 8px; }
.uploader input[type="file"] { color: var(--tusk-dim); }
.uploader-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; }
.uploader-row input[type="file"] { min-width: 0; flex: 1 1 auto; }
#photo-btn { flex: 0 0 auto; }

/* Access-card preview at the top of the vault (a canvas drawn by NarvalioCard). */
.card-preview { margin: 4px 0 14px; }
.card-preview .card-canvas {
  width: 100%; max-width: 340px; height: auto; display: block;
  border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, .4);
}

.file-list { list-style: none; margin: 0; padding: 0; }
.file-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 0; border-top: 1px solid var(--line);
}
.file-meta { display: flex; flex-direction: column; min-width: 0; }
/* break-word (not anywhere): a long name wraps between sensible points instead
   of shattering one character per line when the column is narrow. */
.file-name { overflow-wrap: break-word; }
/* A previewable name is a button styled as text — the natural "open" affordance. */
button.file-name {
  background: none; border: 0; padding: 0; margin: 0; font: inherit; text-align: left;
  color: var(--glacier); cursor: pointer;
}
button.file-name:hover { text-decoration: underline; }
.file-size { color: var(--tusk-dim); font-size: 0.8rem; }
.file-actions { display: flex; gap: 8px; flex-shrink: 0; }

/* Narrow screens: stack the row so the name gets the full width and never has to
   break, and let the actions sit on their own line. */
@media (max-width: 520px) {
  .file-row { flex-direction: column; align-items: stretch; gap: 8px; }
  .file-actions { justify-content: flex-end; }
}

/* In-vault previewer: a decrypted blob shown in place (image / audio / video).
   Colours from the palette; the overlay never leaves the page. */
.viewer-overlay {
  position: fixed; inset: 0; z-index: 80;
  display: flex; align-items: center; justify-content: center; padding: 16px;
  background: rgba(7, 23, 38, .86);
}
.viewer-box {
  display: flex; flex-direction: column; gap: 12px;
  max-width: min(920px, 100%); max-height: 100%;
  background: var(--ink); border: 1px solid var(--line); border-radius: 12px; padding: 14px;
}
.viewer-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.viewer-title { font-family: var(--mono); font-size: 0.9rem; color: var(--tusk); overflow-wrap: break-word; min-width: 0; }
.viewer-close {
  flex: none; background: none; border: 0; color: var(--tusk-dim);
  font-size: 24px; line-height: 1; cursor: pointer; padding: 0 4px;
}
.viewer-close:hover { color: var(--tusk); }
.viewer-media { max-width: 100%; max-height: 74vh; margin: 0 auto; border-radius: 6px; object-fit: contain; }
.viewer-box audio { width: 100%; }
/* PDF: a wider box; pages are drawn by PDF.js into stacked canvases in a scroller. */
.viewer-box-pdf { max-width: min(1000px, 100%); width: 100%; }
.viewer-pdf-pages {
  overflow-y: auto; max-height: 78vh;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  background: #fff; color: #333; padding: 10px; border-radius: 6px;
}
.viewer-pdf-page { max-width: 100%; height: auto; box-shadow: 0 1px 5px rgba(0, 0, 0, .35); }

a { color: var(--glacier); }

/* Virtual keyboard (§7) */
#kbd-toggle { margin: 0 0 12px; }
.kbd { margin: 0 0 16px; }
#kbd-canvas {
  display: block;
  max-width: 100%;
  border: 1px solid var(--line);
  border-radius: 8px;
  cursor: pointer;
  touch-action: manipulation;
}
.kbd-note { margin-top: 10px; }

/* Countdown states — red only when genuinely near expiry */
.countdown-warn { color: var(--glacier); }
.countdown-soon { color: var(--alert); }

/* Storage gauge (addendum §6) */
.gauge { margin: 0 0 14px; }
.gauge-bar {
  height: 8px; background: #0A1F31;
  border: 1px solid var(--line); border-radius: 6px; overflow: hidden;
}
.gauge-fill { height: 100%; width: 0; background: var(--glacier); transition: width 0.2s; }
.gauge-fill.gauge-high { background: var(--alert); }
#gauge-label { margin-top: 6px; }

/* Pages with a footer (landing, legal) + language switcher */
.page-flow { min-height: 100vh; display: flex; flex-direction: column; align-items: center; padding: 24px; }
.page-flow > main { width: 100%; max-width: 460px; margin: 24px 0; }
.page-flow > main.card-wide, .page-flow > main.legal { max-width: 760px; }
/* Meme raison que la ligne ci-dessus : « .page-flow > main » vaut (0,1,1)
   et l'emporte sur une simple classe. Declarer « .card-admin } » plus haut
   ne suffit donc PAS — la regle est parsee, appliquee, et perdue. Constate
   en mesurant la largeur rendue, pas en relisant la feuille. */
.page-flow > main.card-admin { max-width: 1080px; }
.site-footer {
  margin-top: auto; width: 100%; max-width: 760px;
  padding: 16px 4px 4px; border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; gap: 10px 18px; justify-content: space-between;
  font-size: 0.8rem;
}
.site-footer nav { display: flex; flex-wrap: wrap; gap: 6px 14px; }
.site-footer a { color: var(--tusk-dim); text-decoration: none; }
.site-footer a:hover { color: var(--tusk); }
.site-footer a.active { color: var(--glacier); }
/* A backup whose content cannot be trusted, or holds nothing. */
.file-warn { color: var(--alert); }

/* Publisher credit: its own full-width line under the footer navs. */
.footer-credit { flex-basis: 100%; margin: 4px 0 0; color: var(--tusk-dim); font-size: 0.76rem; }

/* Legal pages */
.legal h1 { font-family: var(--mono); font-weight: 500; font-size: 1.4rem; margin-bottom: 8px; }
.legal h2 { font-size: 1.05rem; margin: 26px 0 8px; color: var(--tusk); }
.legal p, .legal li { color: var(--tusk-dim); }
.legal a { color: var(--glacier); }
.legal address { font-style: normal; color: var(--tusk-dim); }
.legal .draft { border-left: 2px solid var(--alert); padding: 8px 14px; color: var(--tusk); margin: 14px 0; font-size: 0.9rem; }
.legal h3 { font-size: 0.98rem; margin: 20px 0 6px; color: var(--tusk); }
.legal ul, .legal ol { padding-left: 1.3em; margin: 10px 0; }
.legal li { margin: 6px 0; }
.legal p { margin: 10px 0; }

/* Admin */
.mono { font-family: var(--mono); letter-spacing: 0.04em; }
.admin-section-title { margin: 22px 0 6px; color: var(--glacier); font-size: 0.9rem; text-transform: uppercase; letter-spacing: 0.08em; }

/* Visually removed but still rendered (unlike [hidden]/display:none, which
   Safari's credential heuristics skip). Used for the setup form's creation
   step once the result screen shows — its password field must stay part of
   the form for the real submission to pair (UID, password). */
.offstage {
  position: absolute !important;
  left: -9999px !important;
  top: 0 !important;
  width: 1px !important;
  height: 1px !important;
  overflow: hidden !important;
}

/* Admin — section tabs. */
.admin-tabs {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
  border-bottom: 1px solid var(--line);
  margin: 18px 0 20px;
}
.admin-tab-btn {
  padding: 10px 16px;
  background: none;
  border: 1px solid transparent;
  border-bottom: none;
  border-radius: 8px 8px 0 0;
  color: var(--tusk-dim);
  font: inherit;
  font-size: 0.92rem;
  cursor: pointer;
  margin-bottom: -1px;
}
.admin-tab-btn:hover { color: var(--tusk); }
.admin-tab-btn.on {
  color: var(--tusk);
  background: var(--ink);
  border-color: var(--line);
  border-bottom: 1px solid var(--ink);
}

/* Sous-onglets — utilisés dans l'admin ET dans l'espace partenaire. Volontairement en pastilles et non
   en onglets à encoche : la forme dit d'un coup d'œil lequel des deux niveaux
   on manipule, ce que deux barres identiques ne feraient pas. */
.subtabs { display: flex; gap: 6px; flex-wrap: wrap; margin: 4px 0 20px; }
.subtab-btn {
  padding: 7px 14px;
  background: none;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--tusk-dim);
  font: inherit;
  font-size: 0.88rem;
  cursor: pointer;
}
.subtab-btn:hover { color: var(--tusk); }
.subtab-btn.on {
  background: var(--glacier);
  border-color: var(--glacier);
  color: var(--abyss);
  font-weight: 500;
}

/* Admin — article editor. */
.admin-head-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.art-meta { display: flex; gap: 16px; flex-wrap: wrap; }
.art-meta .field { flex: 1 1 220px; }
.art-langs { display: flex; gap: 8px; flex-wrap: wrap; margin: 6px 0 14px; }
.art-langs .btn.on { background: var(--glacier); border-color: var(--glacier); color: var(--abyss); font-weight: 500; }
.art-lang-tag { color: var(--glacier); }
/* Admin — mailing list: the add form lays its four fields out in a grid. */
/* align-items:end — filet de sécurité : dès qu'un libellé passe sur deux lignes
   (fenêtre étroite, traduction plus longue), son champ descendait d'une ligne
   sous ceux d'à côté. En alignant les items par le bas, les champs restent sur
   la même ligne quelle que soit la hauteur des libellés. */
.sub-form {
  display: grid;
  gap: 0 16px;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  align-items: end;
}
/* Un <code> contient souvent un chemin ou une URL sans espace : sans cette
   règle il pousse la page entière plus large que l'écran, et tout le contenu
   défile horizontalement. Il vaut mieux qu'il se coupe. */
code { overflow-wrap: anywhere; }

/* Un lot en vente : désignation, quantité, prix, et la case « en vente »
   avec son retrait. La case et le bouton partagent une ligne pour rester à
   hauteur des champs.

   Colonnes DÉCLARÉES plutôt qu'auto-fit : avec « minmax(220px, 1fr) », les
   quatre éléments ne tenaient qu'au-delà de 900 px et la case tombait seule
   à la ligne. Ici la désignation prend le double des deux champs chiffrés,
   qui n'ont besoin que de quelques caractères, et la case ne prend que sa
   place. En dessous de 860 px on rend la main à l'empilement d'origine :
   quatre colonnes serrées deviennent illisibles avant d'être pratiques. */
.offer-row { border-bottom: 1px solid var(--line); padding-top: 4px; }
@media (min-width: 1000px) {
  .offer-row {
    grid-template-columns: minmax(0, 2fr) minmax(0, 1.6fr) minmax(0, 1fr) minmax(0, 1fr) auto;
  }
}
.offer-row:last-of-type { border-bottom: 0; }
.offer-toggle { display: flex; align-items: center; gap: 12px; height: calc(1.5em + 24px); }
.offer-toggle input[type="checkbox"] { width: 20px; height: 20px; flex: none; }

/* Le nom d'un partenaire ouvre sa fiche : c'est un bouton, pour qu'il soit
   atteignable au clavier. Il ne doit pas pour autant RESSEMBLER a un bouton
   au milieu d'une liste — d'ou le depouillement, et le seul soulignement au
   survol pour dire qu'il agit. */
.pa-ouvre {
  background: none; border: 0; padding: 0; margin: 0;
  font: inherit; color: inherit; text-align: left; cursor: pointer;
}
.pa-ouvre:hover, .pa-ouvre:focus-visible { text-decoration: underline; }

/* La fiche en fenetre. « ::backdrop » assombrit la page derriere : sans lui,
   sur un ecran sombre, on ne distingue plus ce qui est au premier plan. */
dialog.card { border: 1px solid var(--line); max-height: 86vh; overflow-y: auto; }
dialog.card::backdrop { background: rgba(0, 0, 0, .6); }

/* Une tâche planifiée : son explication, sa ligne à copier, ses boutons. */
.task-block { margin: 0 0 22px; }
.task-block .cron-url { margin-bottom: 8px; }
.cron-url {
  display: block;
  padding: 10px 12px;
  margin: 4px 0;
  background: #0A1F31;
  border: 1px dashed var(--line);
  border-radius: 8px;
  font-size: 0.82rem;
  overflow-wrap: anywhere;
  color: var(--tusk);
}
.pa-lang-tag { color: var(--glacier); }
#panel-partners textarea {
  width: 100%;
  padding: 11px 12px;
  background: var(--abyss);
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--tusk);
  font: inherit;
  line-height: 1.55;
  resize: vertical;
}
#panel-partners textarea:focus { outline: none; border-color: var(--glacier); }
/* (Les <select> de l'admin sont tous dans un .field : ils héritent de la règle
   commune plus haut. Une règle par panneau les peignait en var(--abyss), le
   fond de page — à côté d'un <input> en #0A1F31 le menu se lisait comme un
   trou et non comme un champ, et chaque nouvel onglet repartait oublié.) */
#panel-manual textarea,
#article-editor textarea {
  width: 100%;
  padding: 11px 12px;
  background: var(--abyss);
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--tusk);
  font: inherit;
  line-height: 1.55;
  resize: vertical;
}
#panel-manual textarea:focus,
#article-editor textarea:focus { outline: none; border-color: var(--glacier); }

/* Password recall row on the setup result screen (masked until the buyer
   asks — shoulder-surfing default). */
.pw-recall {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin: 14px 0;
}
.pw-recall-value {
  font-family: var(--mono);
  letter-spacing: 0.08em;
  background: #0A1F31;
  border: 1px dashed var(--line);
  border-radius: 8px;
  padding: 8px 12px;
  overflow-wrap: anywhere;
}

/* Attention pulse (the "Copier" button on the setup result screen: the buyer
   must not leave without their identifier). Removed by JS on first click. */
.pulse {
  border-color: var(--glacier);
  animation: nrv-pulse 1.6s ease-in-out infinite;
}
@keyframes nrv-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(127, 176, 204, 0.55); }
  70%      { box-shadow: 0 0 0 9px rgba(127, 176, 204, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .pulse { animation: none; }
}

/* In-page modal dialogs (replace window.confirm/prompt): roomy, clearly
   separated touch targets for small screens. */
.nrv-dialog {
  background: var(--ink);
  color: var(--tusk);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 24px;
  width: min(92vw, 420px);
}
.nrv-dialog::backdrop { background: rgba(2, 10, 18, 0.65); }
.nrv-dialog p { margin: 0 0 18px; line-height: 1.5; overflow-wrap: anywhere; }
.nrv-dialog input {
  width: 100%;
  margin-bottom: 18px;
  padding: 11px 12px;
  background: var(--abyss);
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--tusk);
  font: inherit;
}
.nrv-dialog input:focus { outline: none; border-color: var(--glacier); }
.nrv-dialog-actions { display: flex; gap: 14px; justify-content: flex-end; flex-wrap: wrap; }
.nrv-dialog-actions .btn { min-height: 44px; min-width: 110px; }

/* Show/hide password toggle (the eye sits inside the field, on the right). */
.pw-box { position: relative; display: block; }
.pw-box input { width: 100%; padding-right: 44px; }
.pw-eye {
  position: absolute; top: 50%; right: 10px; transform: translateY(-50%);
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0;
  background: none; border: 0; color: var(--tusk-dim); cursor: pointer;
}
.pw-eye:hover, .pw-eye:focus-visible { color: var(--tusk); }
.pw-eye .eye-off { display: none; }
.pw-eye.on .eye { display: none; }
.pw-eye.on .eye-off { display: block; }

/* ── ÉTAT DU SERVEUR ────────────────────────────────────────────────────────
   Un tableau de bord, pas une liste. La première rédaction empilait vingt-cinq
   lignes en deux hauteurs de texte chacune : il fallait tout lire pour savoir
   s'il y avait quelque chose à lire. Un panneau de diagnostic doit rendre un
   VERDICT d'abord, et n'ouvrir le détail qu'ensuite.
   Ajouté le 12/09/2026. */
.etat-verdict {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  margin: 6px 0 14px; padding: 10px 14px;
  border: 1px solid var(--line); border-radius: 6px; background: var(--ink);
}
.etat-verdict .etat-titre { font-weight: 600; }
.etat-verdict.bon .etat-titre { color: var(--go); }
.etat-verdict.alerte .etat-titre { color: var(--alert); }
.etat-verdict .etat-detail { color: var(--tusk-dim); font-size: 0.85rem; }

/* Deux ou trois colonnes selon la largeur, une seule sur téléphone : les
   couples « intitulé / valeur » sont courts, les empiler gaspille l'écran. */
.etat-grille {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(270px, 1fr));
  gap: 0 26px;
}
.etat-groupe {
  grid-column: 1 / -1;
  margin: 14px 0 4px; color: var(--glacier);
  font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.08em;
}
.etat-l {
  display: flex; justify-content: space-between; align-items: baseline; gap: 14px;
  padding: 5px 0; border-bottom: 1px solid var(--line); font-size: 0.85rem;
}
.etat-q { color: var(--tusk-dim); }
.etat-v { text-align: right; overflow-wrap: anywhere; }
/* LE NORMAL RESTE DISCRET. Colorer ce qui va bien autant que ce qui ne va pas
   ferait un sapin de Noël où l'anomalie se perd. */
.etat-v.bon { color: var(--tusk); }
.etat-v.neutre { color: var(--tusk); }
.etat-v.alerte { color: var(--alert); font-weight: 600; }

/* ── Photographies de l'habillage, dans l'administration ─────────────────────
   Déposées et optimisées par le serveur (ImageIngest). Styles ici, jamais en
   attribut inline : la CSP du site interdit le style en ligne. */
.photo-section-title { margin-top: 28px; }
.photo-ingest { display: grid; grid-template-columns: repeat(2, 1fr); gap: 18px; margin-top: 14px; }
@media (max-width: 620px) { .photo-ingest { grid-template-columns: 1fr; } }
.photo-slot { border: 1px solid var(--line); border-radius: 6px; padding: 14px; display: flex; flex-direction: column; gap: 10px; }
.photo-titre { font-family: var(--mono); font-size: 12px; letter-spacing: .08em; color: var(--glacier); text-transform: uppercase; }
.photo-apercu { width: 100%; aspect-ratio: 3/2; object-fit: cover; border-radius: 4px; background: var(--ink); display: block; }
.photo-slot .btn { align-self: flex-start; }
.photo-msg { min-height: 1.2em; }

/* Sélecteur de langue de l'espace partenaire. Cette page n'a ni l'en-tête ni le
   pied marketing (et donc pas leur sélecteur) : on en pose un, discret, au bas
   de chaque carte. Il mène à la même page dans l'autre langue. */
.aff-langs { margin-top: 16px; display: flex; gap: 16px; justify-content: center; flex-wrap: wrap; font-family: var(--mono); font-size: 12px; letter-spacing: .12em; }
.aff-langs a { color: var(--glacier); text-decoration: none; }
.aff-langs a:hover { color: var(--tusk); }
.aff-langs a.on { color: var(--tusk); font-weight: 700; }
