/* ---------- Reset & variables ---------- */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; height: 100%; }
body {
  font-family: "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  color: #1a1d29;
  background: #f5f6fa;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  /* Pied de page "collé" en bas de l'écran (voir layout() dans render.php :
     <body> = <header> + <main> + banniere/vacances + <footer>) — sur une
     page avec peu de contenu (ex: catalogue avec 1-2 produits), <main>
     grandit pour occuper tout l'espace restant plutôt que de laisser le
     footer remonter juste sous le contenu. Aucun changement visuel sur une
     page déjà assez longue pour remplir l'écran. */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
body > main { flex: 1 0 auto; }
:root {
  --red: #2563eb;
  --red-dark: #1e51c0;
  --ink: #1a1d29;
  --ink-soft: #565b70;
  --border: #e3e5ec;
  --surface: #ffffff;
  --bg: #f5f6fa;
  --success: #16a34a;
  --radius: 12px;
}
a { color: inherit; text-decoration: none; }
img { max-width: 100%; display: block; }
h1, h2, h3 { margin: 0 0 12px; line-height: 1.2; }
p { margin: 0 0 12px; }
.container { max-width: 1180px; margin: 0 auto; padding: 0 20px; }

/* ---------- Bannière mode vacances ---------- */
.vacation-banner {
  background: #fff3cd; color: #7a5b00; font-size: 14px; font-weight: 600;
  text-align: center; padding: 10px 0; border-bottom: 1px solid #f0d98c;
}

/* ---------- Header ---------- */
.site-header {
  background: var(--ink);
  color: #fff;
  position: sticky;
  top: 0;
  z-index: 50;
}
.header-inner {
  display: flex;
  align-items: center;
  gap: 24px;
  height: 64px;
}
.brand { display: flex; align-items: center; gap: 10px; font-weight: 700; font-size: 18px; color: #fff; }
.brand-mark { width: 34px; height: 34px; display: block; flex-shrink: 0; }
/* Logo personnalisé (image) depuis /admin/apparence : hauteur fixe, largeur
   libre (contrairement au badge SVG carré par défaut ci-dessus). */
.brand-mark.brand-logo-img { width: auto; height: 34px; max-width: 170px; object-fit: contain; }
.main-nav { display: flex; gap: 20px; margin-left: auto; align-items: center; }
.main-nav a { color: #d7d9e2; font-weight: 500; font-size: 14px; padding: 8px 4px; position: relative; }
.main-nav a:hover, .main-nav a.active { color: #fff; }
.cart-link { display: flex; align-items: center; gap: 6px; color: #d7d9e2; padding: 8px 4px; position: relative; }
.cart-link:hover, .cart-link.active { color: #fff; }
.cart-badge {
  background: var(--red); color: #fff; font-size: 11px; font-weight: 700;
  min-width: 18px; height: 18px; border-radius: 9px; display: inline-flex;
  align-items: center; justify-content: center; padding: 0 4px;
}
.cart-badge:empty { display: none; }
.lang-switch { display: flex; gap: 6px; }
.lang-switch a {
  font-size: 12px; font-weight: 700; color: #a9adbd; padding: 4px 7px; border-radius: 6px;
}
.lang-switch a.lang-active, .lang-switch a:hover { color: #fff; background: rgba(255,255,255,0.1); }

/* ---------- Menu mobile (bouton "burger" + panneau déroulant) ---------- */
.nav-toggle {
  display: none; flex-shrink: 0; width: 40px; height: 40px; margin-left: auto;
  border: none; background: transparent; cursor: pointer; padding: 0;
  border-radius: 8px; flex-direction: column; align-items: center; justify-content: center; gap: 5px;
}
.nav-toggle:hover { background: rgba(255,255,255,0.08); }
.nav-toggle span {
  display: block; width: 22px; height: 2px; background: #fff; border-radius: 2px; transition: transform .2s ease, opacity .2s ease;
}
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

.mobile-menu { display: none; }
.mobile-menu[hidden] { display: none; }
.mobile-menu.open {
  display: flex; flex-direction: column; gap: 2px; padding: 10px 20px 18px;
  background: var(--ink); border-top: 1px solid rgba(255,255,255,0.08);
}
.mobile-menu a { color: #d7d9e2; font-weight: 500; font-size: 15px; padding: 12px 4px; border-radius: 8px; }
.mobile-menu a:hover, .mobile-menu a.active { color: #fff; background: rgba(255,255,255,0.06); }
.mobile-lang-switch { display: flex; gap: 8px; margin-top: 8px; padding-top: 10px; border-top: 1px solid rgba(255,255,255,0.08); }
.mobile-lang-switch a {
  flex: 1; text-align: center; font-size: 13px; font-weight: 700; color: #a9adbd;
  padding: 8px 7px; border-radius: 8px; background: rgba(255,255,255,0.05);
}
.mobile-lang-switch a.lang-active, .mobile-lang-switch a:hover { color: #fff; background: rgba(255,255,255,0.14); }

@media (max-width: 860px) {
  .header-inner { gap: 12px; }
  .main-nav, .lang-switch { display: none; }
  .nav-toggle { display: flex; margin-left: 0; }
  /* Catalogue et langues passent dans le panneau "burger" ; le panier reste
     visible en permanence à côté du bouton burger (pas dans le panneau). */
  .cart-link { margin-left: auto; }
}

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 10px 18px; border-radius: 8px; border: 1px solid var(--border);
  background: #fff; color: var(--ink); font-weight: 600; font-size: 14px;
  cursor: pointer; transition: all .15s ease;
}
.btn:hover { border-color: var(--ink); }
.btn-primary { background: var(--red); border-color: var(--red); color: #fff; }
.btn-primary:hover { background: var(--red-dark); border-color: var(--red-dark); }
.btn-ghost { background: transparent; }
.btn-lg { padding: 13px 26px; font-size: 15px; width: 100%; }
.btn-small { padding: 6px 12px; font-size: 13px; }
.btn[disabled] { opacity: .5; cursor: not-allowed; }
.link-btn {
  background: none; border: none; color: var(--ink-soft); font-size: 13px;
  cursor: pointer; padding: 0; text-decoration: underline;
}
.link-btn.danger { color: var(--red); }

/* ---------- Champs "Choisir un fichier" (admin) ---------- */
/* Le bouton natif du navigateur ("Choisir un fichier" / "Parcourir…") ne se
   personnalise pas comme un <button> normal : on ne peut pas le remplacer
   par un <label>+input caché sans JavaScript pour chaque champ (voir
   data-rte-image-input, seul cas qui utilise déjà ce mécanisme, piloté par
   app.js) — ici on préfère rester 100% CSS, cohérent avec le reste des
   formulaires admin (aucun champ ci-dessous n'a besoin de JS). La pseudo-
   classe standard ::file-selector-button (et son équivalent WebKit historique
   ::-webkit-file-upload-button, pour Safari plus ancien) permet de restyler
   uniquement CE bouton, tout en gardant le texte natif "aucun fichier
   choisi" à côté — d'où deux règles séparées plutôt qu'un sélecteur combiné :
   un navigateur qui ne reconnaît pas l'un des deux pseudo-éléments doit
   ignorer SA règle sans invalider l'autre. Icône "upload" en SVG encodé en
   data URI (mêmes proportions que trashIconSvg()/saveIconSvg() dans
   render.php, mais ici en arrière-plan CSS plutôt qu'en <svg> inline,
   puisqu'un pseudo-élément de bouton natif ne peut pas recevoir de HTML).
   S'applique à tous les champs fichier de l'admin (logo, fonds d'en-tête/
   pied de page/bloc d'accueil/bandeau, photos produit, fichier numérique,
   image SEO...) sans qu'aucun n'ait à être modifié individuellement. */
input[type="file"] {
  font-family: inherit; font-size: 13px; color: var(--ink-soft);
  max-width: 100%;
}
input[type="file"]::file-selector-button {
  font-family: inherit; font-size: 13px; font-weight: 600; color: var(--ink);
  background: #fff no-repeat left 12px center/14px 14px url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23565b70' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4'/%3E%3Cpolyline points='17 8 12 3 7 8'/%3E%3Cline x1='12' y1='3' x2='12' y2='15'/%3E%3C/svg%3E");
  border: 1px solid var(--border); border-radius: 8px;
  padding: 9px 14px 9px 32px; margin-right: 10px;
  cursor: pointer; transition: border-color .15s;
}
input[type="file"]::file-selector-button:hover { border-color: var(--ink); }
input[type="file"]::-webkit-file-upload-button {
  font-family: inherit; font-size: 13px; font-weight: 600; color: var(--ink);
  background: #fff no-repeat left 12px center/14px 14px url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23565b70' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4'/%3E%3Cpolyline points='17 8 12 3 7 8'/%3E%3Cline x1='12' y1='3' x2='12' y2='15'/%3E%3C/svg%3E");
  border: 1px solid var(--border); border-radius: 8px;
  padding: 9px 14px 9px 32px; margin-right: 10px;
  cursor: pointer; transition: border-color .15s;
}
input[type="file"]::-webkit-file-upload-button:hover { border-color: var(--ink); }

/* ---------- Hero ---------- */
.hero {
  position: relative;
  overflow: hidden;
  background: linear-gradient(135deg, #1a1d29 0%, #2c3050 100%);
  color: #fff;
  padding: 64px 0;
  display: flex;
  /* align-items par défaut ("stretch") plutôt que "center" : .hero-inner
     doit occuper toute la hauteur du bloc (utile surtout quand "Hauteur
     minimale" est réglée plus haut que le contenu naturel, Configuration →
     Mise en page) pour que .hero-cta-wrap puisse s'ancrer tout en bas via
     margin-top: auto ci-dessous, plutôt que de rester centré avec le
     titre/sous-titre. */
}
.hero-inner {
  position: relative; z-index: 2; max-width: 640px; width: 100%;
  display: flex; flex-direction: column;
}
/* Groupe titre + sous-titre : occupe tout l'espace disponible restant
   (flex: 1) et centre son propre contenu verticalement à l'intérieur —
   c'est ce qui pousse .hero-cta-wrap (juste après) tout en bas du bloc.
   Sans "Hauteur minimale" réglée, le bloc garde sa hauteur naturelle et le
   bouton reste donc juste sous le sous-titre, comme avant. */
.hero-text-wrap { flex: 1 1 auto; display: flex; flex-direction: column; justify-content: center; }
/* Bouton "Activer un bouton vers le catalogue" (Configuration → Mise en
   page, bloc "Bloc d'accueil") : seul enfant direct de .hero-inner après
   .hero-text-wrap ci-dessus, donc systématiquement ancré tout en bas du
   bloc. */
.hero-inner > .btn-lg { margin-top: 16px; align-self: flex-start; }
.hero h1 { font-size: 38px; }
.hero p { color: #c7c9d9; font-size: 16px; margin-bottom: 24px; }

/* Fond animé du hero : lueurs RGB façon éclairage gamer + trame circuit
   imprimé + puces hexagonales en rotation lente. Purement décoratif (CSS
   only, pas de JS ni d'image externe), masqué aux lecteurs d'écran. */
.hero-bg {
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  pointer-events: none;
}
.hero-grid {
  position: absolute;
  inset: -20% -20%;
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.06) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.06) 1px, transparent 1px);
  background-size: 44px 44px;
  animation: hero-grid-drift 34s linear infinite;
  mask-image: radial-gradient(ellipse 70% 90% at 75% 40%, #000 40%, transparent 80%);
  -webkit-mask-image: radial-gradient(ellipse 70% 90% at 75% 40%, #000 40%, transparent 80%);
}
.hero-orb {
  position: absolute;
  border-radius: 50%;
  filter: blur(48px);
  opacity: 0.5;
}
.hero-orb-1 {
  width: 300px; height: 300px;
  background: var(--red);
  top: -90px; right: 6%;
  animation: hero-float-a 15s ease-in-out infinite;
}
.hero-orb-2 {
  width: 230px; height: 230px;
  background: #3b5bfd;
  bottom: -70px; right: 22%;
  animation: hero-float-b 19s ease-in-out infinite;
}
.hero-orb-3 {
  width: 170px; height: 170px;
  background: #8b3bfd;
  top: 28%; right: 1%;
  animation: hero-float-c 17s ease-in-out infinite;
}
.hero-chip {
  position: absolute;
  border: 1px solid rgba(255, 255, 255, 0.16);
  background: rgba(255, 255, 255, 0.03);
  clip-path: polygon(30% 0, 70% 0, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0 70%, 0 30%);
}
.hero-chip-1 { width: 74px; height: 74px; top: 16%; right: 14%; animation: hero-chip-spin 42s linear infinite; }
.hero-chip-2 { width: 48px; height: 48px; top: 62%; right: 32%; animation: hero-chip-spin 56s linear infinite reverse; }
.hero-chip-3 { width: 36px; height: 36px; top: 42%; right: 7%; animation: hero-chip-spin 30s linear infinite; }
.hero-spark {
  position: absolute;
  width: 3px; height: 3px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 8px 2px rgba(255, 255, 255, 0.8);
  opacity: 0;
  animation: hero-spark-twinkle 6s ease-in-out infinite;
}
.hero-spark-1 { top: 22%; right: 18%; animation-delay: 0s; }
.hero-spark-2 { top: 55%; right: 10%; animation-delay: 1.5s; }
.hero-spark-3 { top: 70%; right: 28%; animation-delay: 3s; }
.hero-spark-4 { top: 12%; right: 34%; animation-delay: 4.4s; }

@keyframes hero-grid-drift {
  from { transform: translate(0, 0); }
  to { transform: translate(44px, 44px); }
}
@keyframes hero-float-a {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(-34px, 26px) scale(1.08); }
}
@keyframes hero-float-b {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(24px, -22px) scale(0.94); }
}
@keyframes hero-float-c {
  0%, 100% { transform: translate(0, 0); }
  50% { transform: translate(-18px, -26px); }
}
@keyframes hero-chip-spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}
@keyframes hero-spark-twinkle {
  0%, 100% { opacity: 0; transform: scale(0.6); }
  50% { opacity: 1; transform: scale(1.4); }
}

@media (max-width: 780px) {
  .hero-bg { opacity: 0.55; }
}
@media (prefers-reduced-motion: reduce) {
  .hero-grid, .hero-orb, .hero-chip, .hero-spark { animation: none; }
}
.section-title { margin: 40px 0 20px; font-size: 22px; }
.page-title { margin: 32px 0 20px; font-size: 26px; }

/* ---------- Articles de blog (accueil, index et fiche article) ---------- */
.articles-section { padding-top: 8px; padding-bottom: 20px; }
.article-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 20px; margin-bottom: 20px;
}
/* Même motif que .listing-card ci-dessus : carte cliquable via un lien
   "étiré" (.article-card-cover) posé par-dessus toute la carte, sauf les
   contrôles interactifs (aucun ici, contrairement à .cart-icon-btn côté
   produit) qui devraient rester au-dessus. */
.article-card {
  position: relative;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  overflow: hidden; display: flex; flex-direction: column; transition: box-shadow .15s, transform .15s;
}
.article-card:hover { box-shadow: 0 8px 24px rgba(20,20,40,0.08); transform: translateY(-2px); }
.article-card-cover { position: absolute; inset: 0; z-index: 1; }
.article-image { position: relative; aspect-ratio: 16/9; background: #eceef4; overflow: hidden; }
.article-image img { width: 100%; height: 100%; object-fit: cover; }
.article-body { padding: 16px; display: flex; flex-direction: column; gap: 8px; flex: 1; }
.article-body h3 { font-size: 16px; margin: 0; }
.article-card-date { font-size: 12px; color: var(--ink-soft); }
.article-excerpt { font-size: 13px; color: var(--ink-soft); margin: 0; line-height: 1.5; }
.article-footer { margin-top: auto; display: flex; align-items: center; justify-content: space-between; padding-top: 8px; font-size: 13px; color: var(--ink-soft); }
.article-readmore { font-weight: 700; color: var(--red); }
.article-card-likes { display: inline-flex; align-items: center; gap: 4px; }
.article-card-likes svg { color: var(--red); }
.articles-see-all { text-align: center; margin-top: 8px; }

/* ---------- Fiche article (/:lang/articles/:slug) ---------- */
.article-detail { max-width: 760px; padding-top: 28px; padding-bottom: 20px; }
.article-detail h1 { font-size: 28px; margin-bottom: 0; }
.article-date { font-size: 13px; color: var(--ink-soft); margin: 6px 0 18px; }
.article-cover { border-radius: var(--radius); overflow: hidden; margin-bottom: 20px; aspect-ratio: 16/7; background: #eceef4; }
.article-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.article-content { font-size: 16px; line-height: 1.7; color: var(--ink); }
.article-content img { border-radius: 8px; margin: 12px 0; }
.article-detail-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 16px; margin-top: 28px; padding-top: 20px; border-top: 1px solid var(--border); }
.article-like-form { margin: 0; }
.article-like-btn {
  display: inline-flex; align-items: center; gap: 8px; padding: 10px 18px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--surface); color: var(--ink); cursor: pointer;
  font-size: 14px; font-weight: 700; transition: background .15s, border-color .15s, color .15s;
}
.article-like-btn:hover { background: var(--bg); }
.article-like-btn.liked { border-color: var(--red); color: var(--red); }
.article-like-btn .article-like-count { color: var(--ink-soft); font-weight: 700; }
.article-like-btn.liked .article-like-count { color: var(--red); }
.article-cta { margin-left: auto; }

/* ---------- Commentaires (sous la fiche article) ---------- */
.article-comments { max-width: 760px; margin: 0 auto; padding: 20px 20px 60px; }
.article-comments h2 { font-size: 20px; margin-bottom: 16px; }
.comment-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 16px 18px; margin-bottom: 12px;
}
.comment-author { font-weight: 700; font-size: 13px; margin: 0 0 4px; color: var(--ink); }
.comment-text { margin: 0; font-size: 14px; line-height: 1.6; color: var(--ink); }
.comment-form-wrap { margin-top: 24px; }
.comment-form-wrap h3 { font-size: 17px; margin-bottom: 4px; }
.comment-form-wrap .checkout-form { margin-top: 12px; }

/* ---------- Avis clients (carousel de réassurance, home page) ---------- */
.reviews-section { padding-top: 8px; padding-bottom: 60px; }
.reviews-carousel { position: relative; }
.reviews-track {
  display: flex; overflow-x: auto; scroll-snap-type: x mandatory;
  scroll-behavior: smooth; -webkit-overflow-scrolling: touch; scrollbar-width: none;
}
.reviews-track::-webkit-scrollbar { display: none; }
.reviews-page {
  flex: 0 0 100%; display: grid; grid-template-columns: 1fr 1fr; gap: 20px;
  scroll-snap-align: start; padding: 2px;
}
@media (max-width: 720px) { .reviews-page { grid-template-columns: 1fr; } }
.review-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 22px; display: flex; flex-direction: column; gap: 10px;
}
.review-stars { color: #f5a524; font-size: 18px; letter-spacing: 2px; line-height: 1; }
.review-comment { font-size: 15px; line-height: 1.6; color: var(--ink); font-style: italic; }
.review-author { font-size: 13px; font-weight: 700; color: var(--ink-soft); margin-top: auto; }
/* Flèches précédent/suivant, centrées SOUS le carousel (voir
   reviewsCarouselHtml() dans routes/public.php) — plus de points de
   pagination à côté, jugés redondants avec les flèches. */
.reviews-nav-row { display: flex; justify-content: center; gap: 14px; margin-top: 18px; }
.reviews-nav {
  flex: 0 0 auto; width: 40px; height: 40px; border-radius: 50%; border: 1px solid var(--border);
  background: var(--surface); font-size: 20px; line-height: 1; cursor: pointer; color: var(--ink);
}
.reviews-nav:hover { background: var(--bg); }

/* Formulaire "Laisser un avis" (page d'accueil, sous le carrousel ci-dessus)
   — voir reviewFormSectionHtml() dans routes/public.php. <details>/<summary>
   natifs (repliable sans JavaScript) : le marqueur triangle par défaut du
   navigateur est masqué au profit d'un bouton stylé, cohérent avec le reste
   du site plutôt qu'un widget natif brut. */
.review-form-section { padding-bottom: 60px; }
/* text-align: center centre le bouton "+ Laisser un avis" ci-dessous (un
   <summary> en inline-flex, donc un simple margin: 0 auto ne suffirait pas
   à le centrer lui-même) ; .review-form-inner réinitialise l'alignement à
   gauche pour le contenu du formulaire une fois déplié, qui ne doit pas
   hériter de ce centrage. */
.review-form-details { max-width: 560px; margin: 0 auto; text-align: center; }
.review-form-toggle {
  list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 8px;
  padding: 12px 22px; border-radius: 999px; border: 1px solid var(--border);
  background: var(--surface); font-weight: 700; font-size: 14px; color: var(--ink);
}
.review-form-toggle::-webkit-details-marker { display: none; }
.review-form-toggle::before { content: "+"; font-size: 16px; line-height: 1; }
.review-form-details[open] .review-form-toggle::before { content: "–"; }
.review-form-toggle:hover { background: var(--bg); }
.review-form-inner {
  margin-top: 18px; background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 24px; text-align: left;
}
.review-form-inner h3 { margin: 0 0 6px; font-size: 18px; }
.review-form-inner .checkout-form { border: none; padding: 0; margin-top: 14px; }

/* Section catalogue (page d'accueil) : fond personnalisable depuis
   Configuration → Mise en page (voir .catalog-section généré à la volée
   dans homeHandler(), CSP oblige) — cette règle est juste le repli par
   défaut avant que le <style nonce> ne s'applique. padding: 1px 0 pour que
   la couleur couvre bien toute la section même quand les marges des
   enfants (h2, .listing-grid) fusionnent avec celles du parent.
*/
.catalog-section { background: var(--bg); padding: 1px 0; }

/* ---------- Listing grid / cards ---------- */
.listing-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 20px; margin-bottom: 40px;
}
/* Bloc "vedette" de la page d'accueil (voir homeHandler() dans
   routes/public.php) : ajoutée en plus de .listing-grid uniquement quand 1 à
   3 produits y sont affichés (siteHomeFeaturedCount() réglable de 1 à 24,
   Configuration → Mise en page). Avec l'auto-fill 1fr par défaut ci-dessus,
   1-3 cartes s'étireraient pour remplir toute la largeur du conteneur au
   lieu de garder une taille de carte normale ; ici auto-fit + une largeur
   maximale par colonne fait que les colonnes vides se réduisent à zéro et
   que les cartes réellement présentes restent centrées, à taille normale.
   Le catalogue complet (chapitre 3.2, souvent bien plus de 3 produits)
   n'utilise jamais cette classe et garde le comportement d'origine. */
.listing-grid-centered {
  grid-template-columns: repeat(auto-fit, minmax(250px, 300px));
  justify-content: center;
}
/* .listing-card est un <div> (pas un <a>) : la carte entière reste
   cliquable vers la fiche produit grâce à .listing-card-cover, un lien
   "étiré" en position absolute qui recouvre toute la carte (voir
   listingCard() dans routes/public.php) — sauf le bouton .cart-icon-btn,
   posé au-dessus (z-index supérieur) pour rester cliquable indépendamment
   et ajouter au panier sans naviguer. */
.listing-card {
  position: relative;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  overflow: hidden; display: flex; flex-direction: column; transition: box-shadow .15s, transform .15s;
}
.listing-card:hover { box-shadow: 0 8px 24px rgba(20,20,40,0.08); transform: translateY(-2px); }
.listing-card-cover { position: absolute; inset: 0; z-index: 1; }
.listing-image { position: relative; aspect-ratio: 4/3; background: #eceef4; overflow: hidden; }
.listing-image img { width: 100%; height: 100%; object-fit: cover; }
.badge {
  position: absolute; top: 10px; left: 10px; background: rgba(26,29,41,0.85); color: #fff;
  font-size: 11px; font-weight: 700; padding: 4px 9px; border-radius: 6px; text-transform: uppercase;
  letter-spacing: .03em;
}
.badge-neuf { background: rgba(217,119,6,0.9); }
.badge-recond { background: rgba(22,163,74,0.9); }
.badge-occasion { background: rgba(37,99,235,0.9); }
.badge-discount { left: auto; right: 10px; background: var(--red); }
.badge-outofstock { top: auto; bottom: 10px; left: 10px; background: rgba(0,0,0,0.75); }
.listing-body { padding: 16px; display: flex; flex-direction: column; gap: 8px; flex: 1; }
.listing-body h3 { font-size: 15px; min-height: 40px; }
.spec-list { list-style: none; padding: 0; margin: 0; font-size: 13px; color: var(--ink-soft); display: flex; flex-direction: column; gap: 3px; }
.spec-list li::before { content: "•"; margin-right: 6px; color: var(--red); font-weight: 700; }
.listing-footer { margin-top: auto; display: flex; align-items: center; justify-content: space-between; padding-top: 8px; }
.price-block { display: flex; flex-direction: column; }
.price { font-size: 18px; font-weight: 800; color: var(--ink); }
.price-original { font-size: 12px; color: #9a9db0; text-decoration: line-through; }
.price-block-lg .price { font-size: 30px; }
.quick-add-form { margin: 0; }
.cart-icon-btn {
  position: relative; z-index: 2;
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; border-radius: 50%; border: 1px solid var(--border);
  background: var(--surface); color: var(--ink); cursor: pointer; transition: background .15s, border-color .15s, transform .15s;
}
.cart-icon-btn:hover { background: var(--red); border-color: var(--red); color: #fff; transform: scale(1.06); }

/* ---------- Menu catégories (catalogue public) ---------- */
.catalog-page.catalog-layout { display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: 28px; align-items: start; }
.catalog-sidebar { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; }
.catalog-sidebar-title { font-size: 15px; margin: 0 0 10px; }
.cat-nav-list, .cat-nav-sublist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.cat-nav-sublist { margin-left: 12px; padding-left: 10px; border-left: 1px solid var(--border); margin-top: 2px; margin-bottom: 4px; }
/* [hidden] doit rester prioritaire sur ".cat-nav-list, .cat-nav-sublist { display: flex }"
   ci-dessus (même spécificité, le "display:none" du user-agent stylesheet
   perd sinon face à une règle d'auteur — voir la même remarque pour
   .mobile-menu[hidden] plus haut dans ce fichier). */
.cat-nav-sublist[hidden] { display: none; }
.cat-nav-row { display: flex; align-items: center; gap: 2px; }
.cat-nav-link {
  flex: 1 1 auto; min-width: 0;
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 7px 8px; border-radius: 8px; font-size: 14px; color: var(--ink-soft);
}
.cat-nav-link:hover { background: var(--bg); color: var(--ink); }
.cat-nav-link.active { background: var(--red); color: #fff; font-weight: 700; }
.cat-nav-count { font-size: 11px; color: var(--ink-soft); }
.cat-nav-link.active .cat-nav-count { color: rgba(255,255,255,0.85); }
.cat-nav-toggle {
  flex: 0 0 auto; width: 26px; height: 26px; display: flex; align-items: center; justify-content: center;
  border: none; background: none; cursor: pointer; color: var(--ink-soft); border-radius: 6px; padding: 0;
}
.cat-nav-toggle:hover { background: var(--bg); color: var(--ink); }
.cat-nav-toggle::before { content: "▸"; font-size: 11px; line-height: 1; display: inline-block; transition: transform .15s; }
.cat-nav-toggle[aria-expanded="true"]::before { transform: rotate(90deg); }

/* ---------- Filters ---------- */
.filters-form { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 18px; margin-bottom: 28px; }
.filters-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 14px; }
.filters-form label { display: flex; flex-direction: column; gap: 6px; font-size: 13px; font-weight: 600; color: var(--ink-soft); }
.filters-form input, .filters-form select {
  padding: 9px 10px; border: 1px solid var(--border); border-radius: 8px; font-size: 14px; font-family: inherit;
}
.filters-actions { display: flex; gap: 10px; margin-top: 14px; }
.empty-state { padding: 40px 0; text-align: center; color: var(--ink-soft); }

/* ---------- Fil d'Ariane (fiche produit) ---------- */
.breadcrumb { margin-bottom: 10px; }
.breadcrumb-list {
  list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; align-items: center;
  gap: 6px; font-size: 13px; color: var(--ink-soft);
}
.breadcrumb-list li { display: flex; align-items: center; gap: 6px; }
.breadcrumb-list a { color: var(--ink-soft); }
.breadcrumb-list a:hover { color: var(--red); text-decoration: underline; }
.breadcrumb-list [aria-current="page"] { color: var(--ink); font-weight: 600; }
.breadcrumb-sep { color: #c7cad6; }

/* ---------- Product page ---------- */
.product-page { display: grid; grid-template-columns: 1.1fr 1fr; gap: 40px; padding: 32px 20px 60px; }
.product-gallery { display: flex; flex-direction: column; gap: 12px; }
.product-image { display: block; background: #eceef4; border-radius: var(--radius); overflow: hidden; aspect-ratio: 4/3; cursor: zoom-in; }
.product-image img { width: 100%; height: 100%; object-fit: cover; }

/* Lightbox 100% CSS (aucun JS) : chaque vignette (.product-image, ci-dessus)
   est un lien vers #photo-N ; le panneau plein écran correspondant, masqué
   par défaut, ne s'affiche que lorsque son ID correspond au fragment de
   l'URL (:target) — voir productHandler() dans routes/public.php. */
.lightbox {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: rgba(15, 17, 26, 0.92);
  padding: 40px 20px;
  align-items: center;
  justify-content: center;
}
.lightbox:target { display: flex; }
.lightbox img { max-width: 100%; max-height: 100%; object-fit: contain; border-radius: 8px; }
.lightbox-close {
  position: absolute;
  top: 16px;
  right: 24px;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  font-size: 28px;
  line-height: 1;
  text-decoration: none;
}
.lightbox-close:hover { background: rgba(255, 255, 255, 0.22); }
.product-info h1 { font-size: 26px; margin-top: 10px; }
.stock-status { font-weight: 700; font-size: 14px; }
.stock-status.in-stock { color: var(--success); }
.stock-status.out-of-stock { color: var(--red); }
.add-to-cart-form { display: flex; gap: 12px; align-items: end; margin: 20px 0; flex-wrap: wrap; }
.qty-picker { display: flex; flex-direction: column; gap: 6px; font-size: 13px; font-weight: 600; color: var(--ink-soft); }
.qty-picker input { width: 80px; padding: 10px; border: 1px solid var(--border); border-radius: 8px; }
.add-to-cart-form .btn { flex: 1; }
.warranty-addon {
  display: flex; flex-direction: column; gap: 4px; width: 100%; order: 3;
  background: #fff8e6; border: 1px solid #ffe8ad; border-radius: 8px; padding: 10px 14px; font-size: 13px;
  font-weight: 600; color: var(--ink); cursor: pointer;
}
.warranty-addon-row { display: flex; align-items: center; gap: 8px; }
.warranty-addon input { margin: 0; flex-shrink: 0; }
.warranty-addon small { display: block; font-weight: 400; color: var(--ink-soft); margin-left: 26px; }
.variant-select-block {
  width: 100%; order: -1; margin: 4px 0 14px;
  display: flex; flex-wrap: wrap; gap: 12px;
}
.variant-select-block .flash { width: 100%; }
.variant-select-label {
  display: block; font-size: 12px; font-weight: 700; color: var(--ink-soft); text-transform: uppercase;
  letter-spacing: .03em; margin-bottom: 6px; flex: 1 1 160px; min-width: 140px;
}
.variant-select-label select {
  display: block; width: 100%; margin-top: 6px; padding: 10px 12px; border: 1px solid var(--border);
  border-radius: 8px; font-size: 14px; font-family: inherit; background: #fff; box-sizing: border-box;
}
.variant-select-label select option:disabled { color: var(--ink-soft); }
.variant-select-block .flash { margin-bottom: 10px; }
.cart-line-variant-tag {
  display: inline-block; font-size: 12px; font-weight: 600; color: var(--ink-soft);
  background: var(--bg); border: 1px solid var(--border); border-radius: 12px; padding: 2px 10px; margin: 2px 0;
}
.spec-table { width: 100%; border-collapse: collapse; margin: 20px 0; }
.spec-table th, .spec-table td { text-align: left; padding: 9px 0; border-bottom: 1px solid var(--border); font-size: 14px; }
.spec-table th { width: 40%; color: var(--ink-soft); font-weight: 600; }
.product-description { color: var(--ink-soft); font-size: 14px; line-height: 1.6; }
.product-description p { margin: 0 0 14px; }
.product-description h3 {
  color: var(--ink); font-size: 15px; font-weight: 800; margin: 24px 0 10px;
  padding-top: 16px; border-top: 1px solid var(--border);
}
.product-description h3:first-child { margin-top: 0; padding-top: 0; border-top: none; }
.product-description ul { margin: 0 0 14px; padding-left: 20px; display: flex; flex-direction: column; gap: 6px; }
.product-description li { font-size: 14px; }

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

@media (max-width: 780px) {
  /* Menu de catégories empilé au-dessus du catalogue plutôt qu'en colonne
     latérale — reste entièrement visible (pas de tiroir/accordéon caché à
     ouvrir), simplement pleine largeur au lieu d'une colonne étroite. */
  .catalog-page.catalog-layout { grid-template-columns: minmax(0, 1fr); gap: 20px; }
}

/* ---------- Cart ---------- */
.cart-page { padding-bottom: 60px; }
.cart-lines { display: flex; flex-direction: column; gap: 12px; margin-bottom: 24px; }
.cart-line {
  display: grid; grid-template-columns: 70px 1fr auto auto auto; gap: 16px; align-items: center;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 12px 16px;
}
.cart-line-image { width: 70px; height: 56px; border-radius: 8px; overflow: hidden; background: #eceef4; }
.cart-line-image img { width: 100%; height: 100%; object-fit: cover; }
.cart-line-info { display: flex; flex-direction: column; gap: 4px; font-size: 14px; }
.cart-line-info a { font-weight: 600; }
.cart-line-warranty-tag { font-size: 12px; color: #a06a00; }
.cart-qty-form { display: flex; align-items: end; gap: 8px; flex-wrap: wrap; }
.cart-qty-form label { display: flex; flex-direction: column; font-size: 12px; color: var(--ink-soft); gap: 4px; }
.cart-qty-form input[type="number"] { width: 64px; padding: 7px; border: 1px solid var(--border); border-radius: 6px; }
.cart-warranty-toggle { flex-direction: row !important; align-items: center; gap: 6px !important; font-size: 12px; }
.cart-warranty-toggle input { width: auto; }
.cart-line-total { font-weight: 700; }
/* Bouton "poubelle" de suppression d'une ligne du panier — même icône et
   même traitement visuel que .existing-image-delete (photos produit, admin)
   / .prod-digital-current-delete (fichier numérique, admin) : simple bouton
   icône plutôt qu'un lien texte "Retirer", pour rester compact à côté du
   total de la ligne. */
.cart-line-remove {
  display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; padding: 0;
  border: 1px solid var(--border); border-radius: 8px; background: var(--surface); color: var(--ink-soft); cursor: pointer;
}
.cart-line-remove:hover { border-color: #d33; color: #d33; background: #fdeceb; }
.cart-summary { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; }
.cart-total-row { display: flex; justify-content: space-between; font-size: 18px; margin-bottom: 16px; }
.cart-total-row.cart-subtotal-row { font-size: 14px; color: var(--ink-soft); margin-bottom: 8px; }
.cart-actions { display: flex; gap: 12px; justify-content: flex-end; flex-wrap: wrap; }
/* Code promo (voir cartHandler() dans routes/public.php) : formulaire de
   saisie compact quand aucun code n'est appliqué, ou bandeau "code appliqué +
   bouton retirer" sinon — jamais les deux à la fois. */
.cart-promo-block { display: flex; align-items: end; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; padding-bottom: 16px; border-bottom: 1px solid var(--border); }
.cart-promo-block label { display: flex; flex-direction: column; font-size: 12px; color: var(--ink-soft); gap: 4px; flex: 1; min-width: 140px; }
.cart-promo-block input[type="text"] { padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px; font-size: 14px; }
.cart-promo-applied { display: flex; justify-content: space-between; align-items: center; font-size: 13px; font-weight: 600; }
.cart-discount-row { color: #1a7f4b; }

@media (max-width: 700px) {
  .cart-line { grid-template-columns: 60px 1fr; grid-template-areas: "img info" "qty qty" "total remove"; }
}

/* ---------- Checkout ---------- */
.checkout-page { padding-bottom: 60px; }
.checkout-grid { display: grid; grid-template-columns: 1.4fr 1fr; gap: 32px; align-items: start; }
.checkout-form { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 24px; display: flex; flex-direction: column; gap: 14px; }
.checkout-form label { display: flex; flex-direction: column; gap: 6px; font-size: 13px; font-weight: 600; color: var(--ink-soft); }
.checkout-form input, .checkout-form select, .checkout-form textarea {
  padding: 10px; border: 1px solid var(--border); border-radius: 8px; font-size: 14px; font-family: inherit;
}
.form-row { display: grid; grid-template-columns: 1fr 1.4fr 1fr; gap: 12px; }
/* Checkout : menu déroulant "Pays" (n'apparaît que si la livraison à
   l'étranger est activée depuis l'admin) — "Suisse / Liechtenstein" en
   première option (livraison domestique), suivie de la liste des autres
   pays (voir countryOptionsHtml() dans countries.php) ; choisir n'importe
   lequel de ces autres pays bascule automatiquement le champ conditionnel
   "canton" (masqué) — voir [data-delivery-zone]/[data-zone-select] dans
   public/app.js. [hidden] doit rester prioritaire sur
   ".checkout-form label { display:flex }" ci-dessus, sinon l'attribut
   natif est silencieusement ignoré. */
.checkout-zone-choice { display: flex; flex-direction: column; }
.zone-field[hidden] { display: none !important; }
.payment-choice { display: flex; gap: 12px; }
.payment-option {
  display: flex; align-items: center; gap: 10px; border: 2px solid var(--red); border-radius: 10px;
  padding: 12px 16px; font-weight: 600; background: #fff5f6; flex-direction: row;
}
/* Badge TWINT : noir officiel #262626 + texte blanc (charte TWINT — voir
   Styleguide_Paylink_EN.pdf, twint.ch), au lieu du noir/jaune précédent qui
   ne correspondait à aucune version officielle du logo. */
.twint-logo {
  background: #262626; color: #fff; font-weight: 800; font-size: 13px; padding: 4px 10px; border-radius: 6px;
  letter-spacing: .02em;
}
.twint-logo-lg { font-size: 22px; padding: 8px 18px; border-radius: 10px; }
/* Regroupe le badge TWINT et l'icône carte l'un à côté de l'autre (au lieu
   de deux blocs noirs empilés) — voir .payment-option ci-dessous, dont le
   flex-direction:column hérité de ".checkout-form label" s'applique aussi à
   ce groupe, mais PAS à son intérieur (row, gap réduit) grâce à cette
   classe dédiée. */
.payment-option-logos { display: flex; align-items: center; gap: 6px; }
/* Icône carte : fond clair et discret (jamais noir, contrairement au badge
   TWINT ci-dessus qui DOIT rester sur son noir officiel) — une icône
   générique de carte bancaire n'a pas de charte à respecter, contrairement
   au logo TWINT. */
.card-logo {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; background: #fff; border: 1px solid var(--border); border-radius: 6px; color: var(--ink-soft);
}
.secure-note { font-size: 13px; color: var(--ink-soft); }
.checkout-summary { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; position: sticky; top: 84px; }
.summary-line { display: flex; justify-content: space-between; font-size: 14px; padding: 6px 0; border-bottom: 1px solid var(--border); }
.summary-line-sub { border-bottom: none; padding: 0 0 6px 14px; font-size: 12px; color: var(--ink-soft); }
.summary-total { display: flex; justify-content: space-between; font-size: 18px; margin-top: 14px; }
.summary-line-discount { color: #1a7f4b; font-weight: 600; }

/* ---------- Legal / CGV page ---------- */
.legal-page { max-width: 820px; padding-bottom: 70px; }
.contact-page { max-width: 620px; padding-bottom: 70px; }
.contact-page .checkout-form { margin-top: 8px; }
/* Piège à robots (anti-spam) : invisible pour un visiteur humain, sans display:none (que certains robots savent détecter). */
.hp-field { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.legal-updated { color: var(--ink-soft); font-size: 13px; margin-bottom: 4px; }
.legal-intro { font-size: 15px; color: var(--ink-soft); margin-bottom: 28px; }
.legal-sections { display: flex; flex-direction: column; gap: 26px; }
.legal-section h2 { font-size: 17px; margin-bottom: 8px; scroll-margin-top: 84px; }
.legal-section p { color: var(--ink-soft); font-size: 14px; margin-bottom: 8px; }

@media (max-width: 860px) {
  .checkout-grid { grid-template-columns: 1fr; }
  .form-row { grid-template-columns: 1fr; }
}

/* ---------- TWINT payment page ---------- */
.twint-page { display: flex; justify-content: center; padding: 48px 20px 70px; }
.twint-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 36px; max-width: 420px; width: 100%; text-align: center;
}
.twint-header { display: flex; flex-direction: column; align-items: center; gap: 12px; margin-bottom: 12px; }
.twint-qr { display: flex; justify-content: center; margin: 20px 0; padding: 16px; background: #fff; border: 1px solid var(--border); border-radius: 10px; }
.twint-amount { display: flex; justify-content: space-between; font-size: 18px; margin: 16px 0; padding: 12px 0; border-top: 1px dashed var(--border); border-bottom: 1px dashed var(--border); }
.order-ref { font-size: 13px; color: var(--ink-soft); }
.demo-disclaimer { font-size: 12px; color: #c07a00; background: #fff8e6; border: 1px solid #ffe8ad; border-radius: 8px; padding: 8px 10px; margin-top: 14px; }
.bank-details { text-align: left; background: var(--bg); border: 1px solid var(--border); border-radius: 10px; padding: 4px 14px; margin: 16px 0; }

/* ---------- Confirmation ---------- */
.confirmation-page { display: flex; justify-content: center; padding: 48px 20px 80px; }
.confirmation-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 40px; max-width: 460px; width: 100%; text-align: center;
}
.confirmation-icon {
  width: 56px; height: 56px; border-radius: 50%; background: var(--success); color: #fff;
  font-size: 28px; display: flex; align-items: center; justify-content: center; margin: 0 auto 16px;
}
.summary-line-list { margin: 20px 0; text-align: left; }

/* ---------- Flash messages ---------- */
.flash { border-radius: 10px; padding: 12px 16px; font-size: 14px; margin-bottom: 16px; }
.flash-error { background: #fdeceb; color: #b8001f; border: 1px solid #f6c4c1; }
.flash-success { background: #eafaf0; color: #14803c; border: 1px solid #b8ecc9; }
.flash-info { background: #eef2ff; color: #3546c9; border: 1px solid #c9d2fb; }
.flash ul { margin: 0; padding-left: 18px; }
.flash-stack { display: flex; flex-direction: column; gap: 10px; margin-bottom: 16px; }

/* ---------- Footer ---------- */
.site-footer { background: var(--ink); color: #a9adbd; padding: 24px 0; margin-top: 40px; font-size: 13px; }
.footer-inner { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 16px; align-items: flex-start; }
.footer-sub { margin-top: 4px; color: #8b8fa3; }
.footer-links { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; }
.footer-link-row { display: flex; gap: 14px; }
.footer-links a { color: #c7c9d9; text-decoration: underline; }
.footer-links a:hover { color: #fff; }
.footer-custom-text { max-width: 420px; }
.footer-payment { display: inline-flex; align-items: center; gap: 8px; }

@media (max-width: 860px) {
  .footer-inner { flex-direction: column; align-items: center; text-align: center; }
  .footer-copy { display: flex; flex-direction: column; align-items: center; }
  .footer-links { align-items: center; }
  .footer-link-row { justify-content: center; flex-wrap: wrap; }
}

/* ---------- Cookie consent banner ---------- */
.cookie-banner {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 100;
  background: var(--ink); color: #e3e5ec; padding: 14px 0;
  box-shadow: 0 -6px 20px rgba(0,0,0,0.25);
}
.cookie-banner-inner { display: flex; align-items: center; justify-content: space-between; gap: 20px; flex-wrap: wrap; }
.cookie-banner-inner p { margin: 0; font-size: 13px; max-width: 760px; color: #c7c9d9; }
.cookie-banner-inner a:not(.btn) { color: #fff; text-decoration: underline; }
.cookie-banner .btn { flex-shrink: 0; }

/* ---------- Admin ---------- */
.admin-body { background: var(--bg); }
/* minmax(0, 1fr), pas juste "1fr" : par défaut la taille minimale d'une
   piste de grille est "auto" (basée sur le contenu), donc un enfant au
   contenu large et non empilable (ex : .admin-nav non wrappée, voir plus
   bas) forcerait toute la piste — et donc TOUTE la page — à s'élargir au-delà
   du viewport plutôt que de simplement déborder DANS sa colonne. Avec
   minmax(0, 1fr), la piste ne dépasse jamais la largeur disponible : tout
   contenu plus large qu'elle doit gérer son propre débordement (scroll
   horizontal local, wrap...) au lieu de faire déborder la page entière. */
.admin-shell { display: grid; grid-template-columns: 230px minmax(0, 1fr); min-height: 100vh; }
.admin-sidebar { background: var(--ink); color: #fff; padding: 20px; display: flex; flex-direction: column; }
/* Rangée logo + bouton "burger" (le burger ne s'affiche qu'en dessous de
   901px, voir .admin-nav-toggle et le @media plus bas — sur desktop c'est
   juste le logo, comme avant). */
.admin-topbar-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 24px; }
.admin-brand { display: flex; align-items: center; gap: 10px; font-weight: 700; }
.admin-nav-toggle { display: none; }
/* Sur desktop, ce conteneur (sélecteur de langue + nav + pied de page) est
   simplement la suite de la colonne .admin-sidebar, toujours visible — c'est
   seulement en dessous de 901px qu'il devient le panneau du menu "burger"
   (voir le @media plus bas, qui le transforme en [hidden] par défaut). flex:1
   pour que .admin-footer (margin-top:auto) reste collé en bas de la barre
   latérale, comme avant cette restructuration. */
.admin-sidebar-menu { display: flex; flex-direction: column; flex: 1; min-height: 0; }

/* ---------- Sélecteur de langue de la console admin (FR/DE/EN) ---------- */
.admin-lang-switch { display: flex; gap: 4px; background: rgba(255,255,255,0.06); border-radius: 8px; padding: 4px; margin-bottom: 20px; }
.admin-lang-btn { flex: 1; text-align: center; padding: 6px 0; border-radius: 6px; font-size: 12px; font-weight: 700; color: #a9adbd; }
.admin-lang-btn:hover { color: #fff; background: rgba(255,255,255,0.1); }
.admin-lang-btn.active { color: #fff; background: var(--red); }
/* Variante utilisée sur la page de connexion (avant authentification), hors
   de la barre latérale sombre : fond clair pour rester lisible sur la carte
   de connexion blanche (voir .login-card). */
.login-card .admin-lang-switch { background: var(--bg); margin: 0 0 20px; }
.login-card .admin-lang-btn { color: var(--ink-soft); }
.login-card .admin-lang-btn:hover { color: var(--ink); background: rgba(0,0,0,0.05); }
.login-card .admin-lang-btn.active { color: #fff; background: var(--red); }

.admin-nav { display: flex; flex-direction: column; gap: 4px; }
.admin-nav a {
  padding: 10px 12px; border-radius: 8px; color: #c7c9d9; font-size: 14px; font-weight: 500;
  display: flex; align-items: center; gap: 10px;
}
.admin-nav a:hover, .admin-nav a.active { background: rgba(255,255,255,0.08); color: #fff; }
.admin-nav a svg { flex: 0 0 auto; opacity: 0.85; }
.admin-nav a:hover svg, .admin-nav a.active svg { opacity: 1; }
/* Entrée "Maintenance" du menu admin (voir adminLayout() dans render.php) :
   liseré rouge discret à gauche + badge "Actif" quand le mode en construction
   est déjà activé, pour qu'on ne l'oublie jamais en naviguant ailleurs dans
   l'admin. Purement visuel, aucun impact sur le comportement. */
.admin-nav a.nav-danger { border-left: 3px solid transparent; }
.admin-nav a.nav-danger:hover, .admin-nav a.nav-danger.active { border-left-color: #e5484d; }
.nav-danger-badge {
  margin-left: auto; background: #e5484d; color: #fff; font-size: 10px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.04em; padding: 2px 7px; border-radius: 999px;
}
.admin-footer { margin-top: auto; padding-top: 16px; border-top: 1px solid rgba(255,255,255,0.1); font-size: 13px; }
.admin-user { color: #a9adbd; margin-bottom: 8px; word-break: break-all; }
.admin-footer .link-btn { color: #c7c9d9; }
.admin-content { padding: 32px 40px; }
.admin-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 24px; }
.admin-header-spaced { margin-top: 32px; }
.admin-header-actions { display: flex; align-items: center; gap: 10px; }
/* Badge affichant le nom du produit à côté du <h1> générique "Modifier le
   produit" (voir listingForm() dans routes/admin.php) — sur la MÊME ligne
   que le titre (jamais une ligne à part) pour ne pas changer la hauteur de
   .admin-header, dont un décalage sticky ailleurs (.product-tabs-nav,
   voir plus bas) dépend d'une hauteur fixe en pixels. min-width:0 avec
   overflow:hidden permet au badge de rétrécir sous sa largeur naturelle
   plutôt que de déborder si l'espace manque ; au-delà de max-width, le nom
   est tronqué avec "…" (titre complet visible au survol via l'attribut
   title). Fond --surface (et non --bg, utilisé par .admin-header lui-même
   en sticky) pour que le badge reste visible une fois l'en-tête figé. */
.admin-header-chip {
  display: inline-block; min-width: 0; max-width: 320px; flex-shrink: 1;
  padding: 5px 12px; background: var(--surface); border: 1px solid var(--border);
  border-radius: 999px; font-size: 13px; font-weight: 600; color: var(--ink-soft);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ---------- Bouton "Enregistrer" compact (icône disquette) ----------
   Un seul bouton par page. Sur une page à un seul formulaire sans onglets
   (fiche "Nouvel avis", champ personnalisé, détail commande...) : dans
   .admin-header-actions, à côté du <h1>. Sur une page à onglets (fiche
   produit, Configuration, Paiements) : dans .tabs-nav-actions, à l'intérieur
   même de la rangée d'onglets (.product-tabs-nav / .cfg-tabs-nav /
   .admin-tabs-nav) plutôt qu'en dessous — poussé tout à droite via
   margin-left:auto, au bord de la page plutôt que dans la largeur limitée du
   formulaire. Un seul bouton visible à la fois par rangée d'onglets : voir
   les règles d'affichage conditionnel plus bas (Configuration, Paiements) —
   voir adminSaveButtonHtml() dans routes/admin.php. */
.btn-save { display: inline-flex; align-items: center; gap: 7px; padding: 9px 18px; font-size: 14px; width: auto; flex-shrink: 0; }
.tabs-nav-actions { margin-left: auto; display: flex; align-items: center; gap: 8px; padding-left: 12px; }
/* Enveloppe des <label> d'onglets (.tabs-nav-scroll, voir listingForm() /
   adminAppearancePage() / twintFormHtml() dans routes/admin.php) : sur
   desktop elle doit rester complètement invisible à la mise en page — ses
   <label> doivent se comporter comme des enfants directs de
   .product-tabs-nav/.cfg-tabs-nav/.admin-tabs-nav, exactement comme avant
   son introduction, pour ne rien changer au rendu desktop. display:contents
   fait disparaître la boîte elle-même tout en gardant ses enfants dans le
   flux flex du parent. C'est seulement sur mobile (voir le @media plus bas)
   qu'elle redevient un vrai conteneur flex scrollable horizontalement,
   séparé de .tabs-nav-actions qui, lui, passe au-dessus. */
.tabs-nav-scroll { display: contents; }

/* Le haut de page (titre + bouton "Enregistrer", et les onglets + bouton
   juste en dessous s'il y en a) reste visible en haut de l'écran même en
   scrollant une longue page (fiche produit, CGV...), plutôt que de
   disparaître et forcer un retour en haut pour changer d'onglet ou
   enregistrer — seulement sur desktop (.admin-sidebar n'est sticky qu'en
   mobile, voir plus bas : on évite ainsi que les deux entrent en collision
   au même top:0). :not(.admin-header-spaced) exclut les en-têtes secondaires
   imbriquées PLUS BAS dans une page déjà figée (ex. "Déclinaisons" sur la
   fiche produit) : seul l'en-tête de tout en haut de chaque page se fige.
   .product-tabs-nav (fiche produit) / .cfg-tabs-nav (Configuration) se
   figent juste en dessous de leur en-tête respective — offsets mesurés au
   pixel près (voir capture d'écran), à réajuster si la hauteur de l'un des
   deux en-têtes change un jour. .admin-tabs-nav (sous-onglets Carte/TWINT vs
   Virement bancaire, imbriqués dans l'onglet "Paiements" de Configuration)
   se fige à son tour juste sous .cfg-tabs-nav, au même offset qu'occupait
   auparavant .form-actions-top. */
@media (min-width: 901px) {
  .admin-header:not(.admin-header-spaced) {
    position: sticky; top: 0; z-index: 20; background: var(--bg);
    padding: 16px 0 12px; margin: -16px 0 12px; border-bottom: 1px solid var(--border);
  }
  .product-tabs-nav,
  .cfg-tabs-nav {
    position: sticky; top: 79px; z-index: 18; background: var(--bg);
  }
  .admin-tabs-nav {
    position: sticky; top: 120px; z-index: 15; background: var(--bg);
    padding: 10px 0; margin: -10px 0 16px;
  }
}
.admin-quick-actions { display: flex; gap: 12px; margin-top: 8px; margin-bottom: 24px; flex-wrap: wrap; }

.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 8px; }

/* Page /admin/statistiques (voir adminStatisticsHandler() dans
   routes/admin.php) : conteneur avec défilement horizontal, même principe
   que .admin-table-wrap ci-dessous — sur mobile, on préfère un graphique
   lisible qu'on fait défiler plutôt qu'un graphique rétréci illisible. La
   largeur/hauteur du <svg> est fixée en pixels côté PHP (voir
   statsBarChartSvg() dans render.php), pas ici : ce conteneur se contente
   de l'entourer et de gérer le défilement. */
.stats-chart-wrap { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; overflow-x: auto; -webkit-overflow-scrolling: touch; margin-bottom: 8px; }
.stats-chart-svg { display: block; color: var(--red); }
.stats-chart-svg rect { fill: currentColor; }
.stats-chart-svg text { fill: var(--ink-soft); }
.stat-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 18px; display: flex; flex-direction: column; gap: 8px; }
.stat-label { font-size: 13px; color: var(--ink-soft); font-weight: 600; display: flex; align-items: center; gap: 7px; }
.stat-label svg { flex: 0 0 auto; opacity: 0.75; }
.stat-value { font-size: 26px; font-weight: 800; }
/* "Meilleure vente" (voir adminDashboardHandler()) : la valeur est un nom
   de produit (texte, potentiellement long) plutôt qu'un nombre — taille
   réduite par rapport à .stat-value, 2 lignes max avant troncature plutôt
   que de casser la mise en page de la carte. .stat-sub : légende
   secondaire sous la valeur (ex : "3× vendu"). */
.stat-value-text {
  font-size: 16px; font-weight: 700; line-height: 1.3;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.stat-sub { font-size: 12px; color: var(--ink-soft); font-weight: 600; }
/* Carte "Meilleure vente" avec aperçu photo (voir listingCoverImageUrl() dans
   models/listings.php + adminDashboardHandler()) : mise en page horizontale
   (vignette à gauche, texte à droite) au lieu de l'empilement vertical par
   défaut de .stat-card — uniquement quand cette carte a une image. "grid-column:
   span 2" lui donne 2x la largeur des autres cartes dans .stat-grid : avec la
   vignette + un nom de produit potentiellement long, la largeur normale d'une
   carte était trop étroite (texte et photo collés). */
.stat-card-with-thumb { flex-direction: row; align-items: center; gap: 16px; grid-column: span 2; }
.stat-card-thumb {
  width: 64px; height: 64px; flex: 0 0 auto; object-fit: cover;
  border-radius: 8px; border: 1px solid var(--border); background: var(--bg);
}
.stat-card-text { display: flex; flex-direction: column; gap: 8px; min-width: 0; flex: 1 1 auto; }
/* Sur mobile, .stat-grid passe naturellement à 1 colonne (chaque carte fait
   déjà 100% de large) : "span 2" n'a alors plus d'effet utile et peut poser
   problème si une grille intermédiaire n'a qu'une seule colonne réelle — on
   le désactive explicitement en dessous de 640px. */
@media (max-width: 640px) {
  .stat-card-with-thumb { grid-column: span 1; }
}
/* Icône devant un <h2> de section (ex : "Commandes récentes" sur le tableau
   de bord) : alignement par vertical-align plutôt que flex, pour ne pas
   perturber .admin-header (déjà flex sur toute la ligne, titre + actions). */
.admin-header h1 svg, .admin-header h2 svg { vertical-align: -3px; margin-right: 8px; }
/* Cartes du tableau de bord menant à une page (produits publiés, commandes,
   commandes en attente — voir adminDashboardHandler()) : ce sont des <a>
   plutôt que des <div>, contrairement aux 2 autres cartes (chiffre
   d'affaires : pas de page dédiée ; mode TWINT : déjà son propre lien
   interne sur le badge) — le survol/la transition ne cible donc QUE les
   vraies cartes cliquables via le sélecteur "a.stat-card", pas les 2 autres. */
a.stat-card { transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease; }
a.stat-card:hover { border-color: var(--red); box-shadow: 0 4px 14px rgba(0,0,0,0.08); transform: translateY(-1px); }

/* Enveloppe scrollable (produits, stocks, commandes, avis, champs) : sur
   petit écran, un tableau à 5-6 colonnes ne rentre pas dans la largeur
   disponible. Plutôt que de laisser le tableau élargir toute la page
   (illisible + oblige à scroller la page entière juste pour voir la colonne
   ACTIONS), le scroll horizontal reste local à cette enveloppe — même
   principe que .cfg-tabs-nav/.product-tabs-nav (voir plus bas), déjà
   scrollables sur mobile. */
.admin-table-wrap { width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.admin-table { width: 100%; min-width: 540px; border-collapse: collapse; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
.admin-table th, .admin-table td { padding: 12px 16px; text-align: left; border-bottom: 1px solid var(--border); font-size: 14px; }
.admin-table th { background: #f0f1f6; font-size: 12px; text-transform: uppercase; letter-spacing: .03em; color: var(--ink-soft); }
/* .listing-cell est un <td> : le flex vit sur .listing-cell-inner (un div
   à l'intérieur), jamais directement sur le <td> — un <td> en display:flex
   casse l'alignement de hauteur de ligne du tableau (le trait horizontal de
   bordure de CETTE cellule ne correspondait plus à celui des autres
   cellules de la même ligne, d'où un "double trait" légèrement décalé sur
   /admin/annonces). */
.listing-cell { vertical-align: middle; }
.listing-cell-inner { display: flex; align-items: center; gap: 12px; }
.thumb { width: 48px; height: 40px; object-fit: cover; border-radius: 6px; background: #eceef4; }
.thumb-empty { background: #eceef4; }
.listing-cell-title { font-weight: 600; }
.listing-cell-sub { font-size: 12px; color: var(--ink-soft); }
/* Ligne de sous-catégorie dans le tableau /admin/categories : légèrement
   indentée et atténuée pour se distinguer visuellement de la catégorie
   principale juste au-dessus (voir adminCategoriesHandler() dans
   routes/admin.php). */
.cat-sub-row { padding-left: 32px; color: var(--ink-soft); }
.pill { font-size: 11px; font-weight: 700; padding: 4px 9px; border-radius: 20px; text-transform: uppercase; }
.pill-published { background: #eafaf0; color: #14803c; }
.pill-draft { background: #f0f1f6; color: var(--ink-soft); }
.pill-order-pending { background: #fff8e6; color: #c07a00; }
.pill-order-paid { background: #eafaf0; color: #14803c; }
.pill-order-shipped { background: #eef2ff; color: #3546c9; }
.pill-order-cancelled { background: #fdeceb; color: #b8001f; }
/* Même remarque que pour .listing-cell : le flex vit sur .actions-cell-inner,
   jamais directement sur le <td>, sinon le trait de bordure de cette
   dernière colonne se décale par rapport aux autres colonnes de la ligne. */
.actions-cell { vertical-align: middle; }
.actions-cell-inner { display: flex; gap: 12px; align-items: center; }

/* ---------- Réglages TWINT (admin) ---------- */
.badge-status { display: inline-block; font-size: 12px; font-weight: 700; padding: 4px 10px; border-radius: 20px; text-decoration: none; }
.badge-ok { background: #eafaf0; color: #14803c; }
.badge-warn { background: #fff3e0; color: #b8560a; }
.badge-danger { background: #fdeceb; color: #b8001f; }
/* Étoile "mis en avant sur l'accueil" (liste des annonces, voir
   adminListingsHandler()) : badge compact plutôt qu'une pastille large, un
   seul caractère à afficher. */
.badge-featured { padding: 2px 6px; background: #fff8e1; color: #a86b00; }

/* Carte générique pour un petit bloc de réglages au-dessus d'un tableau
   admin (voir Admin → Articles → Commentaires, adminArticleCommentsHandler()
   dans routes/admin.php) — même apparence que .stat-card, juste réutilisable
   pour un formulaire plutôt qu'un chiffre. */
.admin-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 18px 20px; margin-bottom: 20px;
}
.settings-form { display: flex; flex-direction: column; gap: 10px; }

/* ---------- Recherche / filtres / pagination (listes admin) ---------- */
.admin-filters-bar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-bottom: 16px;
}
.admin-filters-bar input[type="search"] {
  flex: 1 1 260px; min-width: 200px; padding: 9px 12px; border: 1px solid var(--border);
  border-radius: 8px; font-size: 14px;
}
.admin-filters-bar select {
  padding: 9px 12px; border: 1px solid var(--border); border-radius: 8px; font-size: 14px; background: var(--surface);
}
.pagination { display: flex; align-items: center; justify-content: center; gap: 16px; margin-top: 20px; }
.pagination-link { font-size: 14px; font-weight: 600; color: var(--red); text-decoration: none; }
.pagination-link:hover { text-decoration: underline; }
.pagination-link.disabled { color: var(--ink-soft); opacity: 0.5; pointer-events: none; }
.pagination-current { font-size: 13px; color: var(--ink-soft); }

/* ---------- Actions groupées (liste produits admin) ---------- */
.admin-bulk-bar {
  display: none; align-items: center; gap: 12px; margin-bottom: 12px; padding: 10px 14px;
  background: var(--bg); border: 1px solid var(--border); border-radius: 8px; font-size: 14px;
}
.admin-bulk-bar.admin-bulk-bar-active { display: flex; }
.admin-bulk-bar .admin-bulk-count { font-weight: 600; color: var(--ink-soft); }
.admin-table td.select-cell, .admin-table th.select-cell { width: 32px; }
.twint-settings-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 24px; max-width: 720px; margin-bottom: 24px;
}
/* Bloc "Langues supplémentaires" (voir customLangManagementHtml() dans
   routes/admin.php) — un peu plus large que .twint-settings-card standard
   car il regroupe le contenu du site ET les ~200 textes d'interface d'une
   langue en un seul bloc. */
.customlang-block { max-width: 900px; }
.customlang-block-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 8px; }
.customlang-code-badge {
  font-size: 12px; font-weight: 600; color: var(--ink-soft); background: var(--bg);
  border: 1px solid var(--border); border-radius: 999px; padding: 2px 10px; margin-left: 8px;
  text-transform: uppercase; vertical-align: middle;
}
.customlang-add-form { display: flex; align-items: flex-end; gap: 14px; flex-wrap: wrap; }
.customlang-add-form label { display: flex; flex-direction: column; gap: 6px; font-size: 13px; font-weight: 600; color: var(--ink-soft); }
/* Groupes repliables des textes d'interface (voir customLangKeyGroups()) —
   même principe <details>/<summary> natif que .review-form-details, mais
   avec le triangle de repli par défaut du navigateur (pas besoin d'un style
   personnalisé ici, contexte admin uniquement). */
.customlang-group { border: 1px solid var(--border); border-radius: 8px; padding: 10px 14px; margin-bottom: 10px; }
.customlang-group summary { cursor: pointer; font-weight: 700; font-size: 13px; }
.customlang-group-count { color: var(--ink-soft); font-weight: 400; }
.customlang-group .form-grid { margin-top: 12px; }
.twint-mode-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin: 18px 0; }
/* Variante à 3 colonnes (choix du type de fond du bloc d'accueil, admin
   Apparence) — réutilise le style visuel de .twint-mode-option. */
.hero-bg-type-row { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 14px; margin: 18px 0; }
/* Panneau de réglage (couleur / image) du fond du bloc d'accueil : affiché
   uniquement pour le type actuellement sélectionné (voir app.js). Une classe
   plutôt qu'un style inline, pour rester compatible avec la police CSP du
   site (style-src 'self', sans 'unsafe-inline'). */
.hero-bg-panel { display: none; margin-top: 12px; }
.hero-bg-panel.hero-bg-panel-visible { display: block; }
.apparence-logo-preview { max-height: 140px; max-width: 220px; object-fit: contain; background: #1a1d29; padding: 8px; border-radius: 8px; display: block; }
.apparence-logo-preview-fallback { width: 60px; }
/* Aperçu du favicon (voir siteFaviconUrl()) : volontairement affiché en tout
   petit (contrairement à .apparence-logo-preview ci-dessus) — c'est une icône
   d'onglet de navigateur, pas un logo d'en-tête ; un aperçu de même taille
   induirait en erreur sur le rendu réel une fois importé. */
.apparence-favicon-preview { width: 48px; height: 48px; object-fit: contain; background: #1a1d29; padding: 6px; border-radius: 8px; display: block; }
.apparence-favicon-preview-fallback { width: 48px; }
/* Redimensionnement du logo (voir le bloc "Redimensionnement du logo" dans
   app.js) : wrapper en display:inline-block pour que la poignée, positionnée
   en absolu, s'aligne sur le bord bas-droit de l'image quelle que soit sa
   largeur (variable, object-fit: contain). */
.apparence-logo-resize { position: relative; display: inline-block; }
.apparence-logo-resize-handle {
  position: absolute; right: 2px; bottom: 2px; width: 16px; height: 16px;
  background: #2563eb; border: 2px solid #fff; border-radius: 3px;
  cursor: ns-resize; box-shadow: 0 1px 3px rgba(0,0,0,.35);
}
.apparence-logo-resize-hint { margin-top: 8px; }
.apparence-hero-bg-preview { max-height: 80px; border-radius: 8px; margin: 8px 0; display: block; }
.admin-back-link { display: block; margin-top: 8px; }
.addon-color-field { display: flex; align-items: center; gap: 12px; margin: 12px 0; font-weight: 600; }
.addon-color-field input[type="color"] { width: 44px; height: 32px; padding: 0; border: 1px solid var(--border); border-radius: 6px; cursor: pointer; }

/* ---------- Champs texte admin : plus spacieux ---------- */
.listing-form textarea, .form-grid textarea, .twint-settings-card textarea, .seo-card textarea {
  width: 100%; padding: 12px; border: 1px solid var(--border); border-radius: 8px;
  font-size: 14px; font-family: inherit; line-height: 1.5; resize: vertical; min-height: 90px;
  box-sizing: border-box;
}
/* CSS personnalisé (Configuration → Mise en page → "CSS avancé", voir
   siteCustomCss()) : police à chasse fixe et plus de hauteur par défaut que
   les autres textareas admin, plus lisible pour du code. */
.custom-css-textarea { font-family: ui-monospace, "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; min-height: 180px; }
/* Champs texte des cartes SEO (.seo-card) : leur <label> n'a pas le
   display:flex des autres formulaires admin (.form-grid label...), donc le
   texte "libellé" ci-dessus et le textarea ci-dessous ne s'empilaient pas
   correctement sans cette règle — voir adminSeoPage()/$metaPanel dans
   routes/admin.php. */
.seo-card label { display: block; margin-bottom: 6px; }
/* Titre de la page Contact (/admin/contact) : même largeur pleine que les
   textarea ci-dessus, pour que le libellé suivant ("Texte d'introduction")
   passe bien à la ligne au lieu de s'afficher à côté du champ. */
.contact-title-input {
  width: 100%; padding: 12px; border: 1px solid var(--border); border-radius: 8px;
  font-size: 14px; font-family: inherit; box-sizing: border-box; margin-bottom: 4px;
}

/* ---------- Conditions générales (admin) : onglets + éditeur riche ---------- */
.cgv-form { max-width: 100%; }
.cgv-tabs { margin-top: 8px; }
.cgv-tab-buttons { display: flex; gap: 8px; margin-bottom: 14px; border-bottom: 1px solid var(--border); }
.cgv-tab-btn {
  border: none; background: none; padding: 10px 16px; font-size: 14px; font-weight: 600;
  color: var(--ink-soft); cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.cgv-tab-btn.active { color: var(--ink); border-bottom-color: var(--red); }
.cgv-tab-btn:hover { color: var(--ink); }
.cgv-copy-fr-btn { margin-left: auto; align-self: center; font-size: 13px; white-space: nowrap; }
.rte { border: 1px solid var(--border); border-radius: 10px; overflow: hidden; background: var(--surface); position: relative; }
.rte-toolbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px; padding: 8px;
  background: var(--bg); border-bottom: 1px solid var(--border);
}
.rte-toolbar select {
  padding: 6px 8px; border: 1px solid var(--border); border-radius: 6px; font-size: 13px;
  font-family: inherit; background: var(--surface); max-width: 130px;
}
.rte-btn {
  border: 1px solid transparent; background: none; border-radius: 6px; padding: 6px 10px;
  font-size: 14px; cursor: pointer; color: var(--ink); line-height: 1;
}
.rte-btn:hover { background: var(--surface); border-color: var(--border); }
.rte-btn-reset { font-size: 12px; color: var(--ink-soft); }
.rte-color-btn {
  display: inline-flex; align-items: center; gap: 4px; border: 1px solid var(--border);
  border-radius: 6px; padding: 4px 8px; font-size: 13px; font-weight: 700; cursor: pointer; background: var(--surface);
}
.rte-color-btn input[type="color"] { width: 20px; height: 20px; padding: 0; border: none; background: none; cursor: pointer; }
.rte-sep { width: 1px; height: 22px; background: var(--border); margin: 0 4px; }
.rte-content {
  min-height: 440px; max-height: 720px; overflow-y: auto; padding: 20px 24px;
  font-size: 15px; line-height: 1.6; color: var(--ink);
}
.rte-content:focus { outline: none; background: #fffef8; }
/* Variante compacte : titre/sous-titre du bloc d'accueil (hero), textes
   courts d'une à quelques lignes — la hauteur spacieuse par défaut (pensée
   pour les CGV) serait disproportionnée ici. */
.rte-content-compact { min-height: 60px; max-height: 200px; padding: 12px 16px; }
.rte-content h1 { font-size: 24px; margin: 0 0 12px; }
.rte-content h2 { font-size: 19px; margin: 22px 0 10px; }
.rte-content h3 { font-size: 16px; margin: 18px 0 8px; }
.rte-content p { margin: 0 0 12px; }
.rte-content img { max-width: 100%; border-radius: 6px; margin: 8px 0; cursor: pointer; }
/* Sélection + redimensionnement d'une image insérée dans l'éditeur (voir le
   bloc "Sélection + redimensionnement des images" dans app.js) : calque
   positionné par-dessus l'image via JS (getBoundingClientRect()), jamais un
   enfant du contenu éditable — rien de tout ceci n'atterrit dans le HTML
   enregistré. La poignée et le bouton de suppression restent DANS le cadre
   (pas de débordement négatif) pour ne jamais se faire couper par
   .rte { overflow: hidden }. */
.rte-img-overlay {
  position: absolute; border: 2px solid #2563eb; border-radius: 6px;
  pointer-events: none; box-sizing: border-box; z-index: 5;
}
.rte-img-overlay[hidden] { display: none; }
.rte-img-handle {
  position: absolute; right: 2px; bottom: 2px; width: 14px; height: 14px;
  background: #2563eb; border: 2px solid #fff; border-radius: 3px;
  cursor: nwse-resize; pointer-events: auto; box-shadow: 0 1px 3px rgba(0,0,0,.35);
}
.rte-img-delete {
  position: absolute; top: 4px; right: 4px; width: 22px; height: 22px;
  border-radius: 50%; border: none; background: rgba(20,20,25,.75); color: #fff;
  font-size: 15px; line-height: 1; cursor: pointer; pointer-events: auto;
  display: flex; align-items: center; justify-content: center; padding: 0;
}
.rte-img-delete:hover { background: #dc2626; }
.rte-content ul, .rte-content ol { margin: 0 0 12px; padding-left: 22px; }
/* Alignement du texte riche : execCommand(justify*) pose toujours un
   style="text-align:…" inline, que la CSP du site bloque. On le remplace
   côté JS par une de ces classes statiques (voir app.js normalizeRteAlignment),
   qui elles s'appliquent aussi bien dans l'éditeur admin que sur le rendu
   public (pied de page, hero, CGV, description produit). */
.rte-align-left { text-align: left; }
.rte-align-center { text-align: center; }
.rte-align-right { text-align: right; }
.rte-align-justify { text-align: justify; }
.twint-mode-option {
  display: flex; gap: 10px; align-items: flex-start; border: 1px solid var(--border); border-radius: 10px;
  padding: 14px; cursor: pointer; transition: border-color .15s, background .15s;
}
.twint-mode-option:has(input:checked) { border-color: var(--red); background: #fff5f6; }
.twint-mode-option input { margin-top: 4px; }
.twint-mode-option p { margin: 4px 0 0; font-size: 13px; color: var(--ink-soft); }
.twint-confirm-check { display: flex; align-items: flex-start; gap: 8px; font-size: 13px; color: var(--ink-soft); margin-top: 6px; }
.condition-toggle-group { background: #f8f9fc; border: 1px solid var(--border); border-radius: 8px; padding: 12px 14px; margin: 4px 0 6px; }
.condition-toggle-group .field-hint { margin: 0 0 6px; }
.twint-settings-help { background: #f0f1f6; border-radius: var(--radius); padding: 18px 22px; max-width: 720px; font-size: 13px; color: var(--ink-soft); }
.twint-settings-help h2 { font-size: 15px; margin-bottom: 8px; }
.twint-settings-help code { background: #fff; border: 1px solid var(--border); border-radius: 4px; padding: 1px 5px; font-size: 12px; }
@media (max-width: 700px) {
  .twint-mode-row, .hero-bg-type-row { grid-template-columns: 1fr; }
}

/* ---------- Paliers de livraison au poids (admin) : tuiles ---------- */
.shipping-tiers-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 14px; margin: 4px 0 22px;
}
.shipping-tier-card {
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 16px; display: flex; flex-direction: column; gap: 10px;
  transition: border-color .15s, box-shadow .15s;
}
.shipping-tier-card:focus-within, .shipping-tier-card:hover {
  border-color: var(--red); box-shadow: 0 0 0 1px var(--red);
}
.shipping-tier-top { display: flex; align-items: center; gap: 8px; }
.shipping-tier-icon {
  width: 34px; height: 34px; border-radius: 9px; background: var(--surface);
  border: 1px solid var(--border); display: flex; align-items: center; justify-content: center;
  font-size: 16px; flex-shrink: 0;
}
.shipping-tier-badge {
  font-size: 11px; font-weight: 700; color: var(--ink-soft); text-transform: uppercase; letter-spacing: .03em;
}
.shipping-tier-label { display: flex; flex-direction: column; gap: 8px; font-size: 13px; font-weight: 700; color: var(--ink); }
.shipping-tier-input-wrap {
  display: flex; align-items: stretch; border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface); overflow: hidden;
}
.shipping-tier-input-wrap input {
  border: none; flex: 1; min-width: 0; padding: 10px 12px; font-size: 15px; font-weight: 700;
  color: var(--ink); background: transparent; font-family: inherit;
}
.shipping-tier-input-wrap input:focus { outline: none; }
.shipping-tier-suffix {
  padding: 0 12px; font-size: 12px; font-weight: 700; color: var(--ink-soft);
  background: var(--bg); border-left: 1px solid var(--border); display: flex; align-items: center;
}

/* ---------- Virement bancaire (admin) : bloc "activer" + tuiles de champs ---------- */
.bank-enable-option { max-width: 720px; margin: 6px 0 20px; }
.bank-enable-option span { font-size: 14px; font-weight: 700; color: var(--ink); }
.bank-field-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 14px; margin-bottom: 20px;
}
.bank-field-card {
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 14px 16px; display: flex; flex-direction: column; gap: 8px;
  transition: border-color .15s, box-shadow .15s;
}
.bank-field-card:focus-within, .bank-field-card:hover {
  border-color: var(--red); box-shadow: 0 0 0 1px var(--red);
}
.bank-field-top { display: flex; align-items: center; gap: 8px; }
.bank-field-icon {
  width: 30px; height: 30px; border-radius: 8px; background: var(--surface);
  border: 1px solid var(--border); display: flex; align-items: center; justify-content: center;
  font-size: 14px; font-weight: 700; color: var(--ink-soft); flex-shrink: 0;
}
.bank-field-badge {
  font-size: 11px; font-weight: 700; color: var(--ink-soft); text-transform: uppercase; letter-spacing: .03em;
}
.bank-field-input {
  border: 1px solid var(--border); border-radius: 8px; background: var(--surface);
  padding: 10px 12px; font-size: 14px; font-weight: 600; color: var(--ink); font-family: inherit;
  width: 100%;
}
.bank-field-input:focus { outline: none; border-color: var(--red); }
.bank-field-input-mono { font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; letter-spacing: .02em; }
.bank-note-field { display: flex; flex-direction: column; gap: 6px; font-size: 13px; font-weight: 600; color: var(--ink-soft); max-width: 720px; }

/* ---------- SEO & référencement (admin) ---------- */
.seo-intro { max-width: 760px; color: var(--ink-soft); margin-bottom: 20px; }
.seo-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 22px 24px; max-width: 760px; margin-bottom: 20px;
}
.seo-card h2 { font-size: 16px; margin-bottom: 12px; }
.seo-card > p { font-size: 13px; color: var(--ink-soft); margin-bottom: 12px; }
.seo-list { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 10px; font-size: 14px; }
.seo-list li { line-height: 1.55; }
.seo-list-numbered { padding-left: 22px; }
.seo-list a { text-decoration: underline; color: var(--red); }
.seo-list a:hover { color: var(--red-dark); }
.seo-list code { background: #f0f1f6; border-radius: 4px; padding: 1px 5px; font-size: 12px; }

.listing-form { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 24px; max-width: 900px; }
.listing-form-narrow { max-width: 420px; }
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.form-grid-flush { padding: 0; }
.form-grid .span-2 { grid-column: 1 / -1; }
/* Titre de sous-section léger, regroupant plusieurs cartes dans une même
   grille (ex: Configuration → Mise en page, voir adminAppearancePage()) —
   volontairement plus discret qu'un <h2> de carte : juste une ligne de
   séparation avec un libellé, pas un vrai bloc encadré. Le premier de la
   grille n'a pas besoin de la marge/ligne du dessus (voir .cfg-section-first
   ci-dessous), pour ne pas laisser un espace vide en haut de la page. */
.cfg-section-title {
  grid-column: 1 / -1;
  font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--ink-soft);
  margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--border);
}
.cfg-section-title.cfg-section-first { margin-top: 0; padding-top: 0; border-top: none; }
.form-grid label { display: flex; flex-direction: column; gap: 6px; font-size: 13px; font-weight: 600; color: var(--ink-soft); }
/* Libellé d'un champ qui ne peut PAS être un <label> (ex: éditeur riche —
   voir le commentaire dans articleForm()/$panelRenderer, routes/admin.php :
   un <label> englobant la barre d'outils de l'éditeur associerait
   implicitement son premier <select>, volant le focus au clic). Reprend la
   même typographie que ".form-grid label" ci-dessus, en display:block pour
   forcer le retour à la ligne avant le champ qui suit (un <span> nu reste
   "inline" par défaut). */
.form-grid .field-label-inline { display: block; margin-bottom: 6px; font-size: 13px; font-weight: 600; color: var(--ink-soft); }
/* Sous-section "Référencement (SEO)" dans chaque onglet de langue de la
   fiche article (voir articleForm()/$panelRenderer, routes/admin.php) :
   simple séparateur visuel pour la distinguer des champs de contenu
   principaux (titre/extrait/contenu/bouton) juste au-dessus. */
.article-seo-fields { margin-top: 4px; padding-top: 18px; border-top: 1px dashed var(--border); }
.article-seo-heading { font-weight: 700; font-size: 14px; margin: 0 0 2px; }
.article-seo-tips { margin-bottom: 20px; }
.form-grid input, .form-grid select, .form-grid textarea {
  padding: 10px; border: 1px solid var(--border); border-radius: 8px; font-size: 14px; font-family: inherit;
}
.field-hint { font-size: 12px; color: var(--ink-soft); margin: 6px 0; }
/* Espace un <label> du bloc précédent (ex : "Texte du bouton" juste après
   l'éditeur riche du sous-titre du bloc d'accueil, sans quoi les deux se
   touchent visuellement) — display:block nécessaire pour que margin-top
   s'applique (un <label> est inline par défaut). */
.field-label-spaced { display: block; margin-top: 16px; }
.existing-images { display: flex; gap: 10px; flex-wrap: wrap; margin: 10px 0; }
.existing-image {
  display: flex; flex-direction: column; gap: 4px; font-size: 12px;
  position: relative; border: 1px solid var(--border); border-radius: 8px; padding: 6px; background: var(--surface);
}
.existing-image:has(input[name="cover_image_id"]:checked) { border-color: var(--red); box-shadow: 0 0 0 1px var(--red); }
.existing-image img { width: 90px; height: 70px; object-fit: cover; border-radius: 6px; }
.cover-tag {
  position: absolute; top: 2px; left: 2px; background: var(--red); color: #fff; font-size: 9px;
  font-weight: 700; padding: 2px 6px; border-radius: 4px; text-transform: uppercase; letter-spacing: .02em;
}
.existing-image-actions { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.existing-image-radio { display: flex; align-items: center; gap: 4px; font-weight: 400; cursor: pointer; align-self: end; }
.existing-image-delete {
  display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; padding: 0;
  border: 1px solid var(--border); border-radius: 6px; background: var(--surface); color: var(--ink-soft); cursor: pointer;
}
.existing-image-delete:hover { border-color: #d33; color: #d33; background: #fdeceb; }

/* ---------- Fichier numérique (onglet dédié de listingForm()) ----------
   Carte "fichier actuellement enregistré" : icône agrandie à gauche, nom du
   fichier avec un peu d'air (libellé au-dessus, nom en dessous), bouton
   "poubelle" de suppression INSTANTANÉE à droite (voir
   adminDeleteListingDigitalFileHandler() — même principe que
   .existing-image-delete pour les photos : formaction/formmethod/
   formnovalidate + data-confirm, pas de case à cocher appliquée seulement à
   l'enregistrement du formulaire). */
.prod-digital-current {
  display: flex; align-items: center; gap: 14px; margin: 14px 0 4px;
  padding: 14px 16px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg);
}
.prod-digital-current-icon {
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
  width: 42px; height: 42px; border-radius: 8px; background: var(--surface); border: 1px solid var(--border); color: var(--ink-soft);
}
.prod-digital-current-icon svg { width: 20px; height: 20px; }
.prod-digital-current-info { display: flex; flex-direction: column; gap: 4px; min-width: 0; flex: 1; }
.prod-digital-current-label { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .03em; color: var(--ink-soft); }
.prod-digital-current-name { font-size: 14px; font-weight: 600; color: var(--ink); overflow-wrap: anywhere; }
.prod-digital-current-delete {
  display: flex; align-items: center; justify-content: center; flex-shrink: 0; width: 34px; height: 34px; padding: 0;
  border: 1px solid var(--border); border-radius: 6px; background: var(--surface); color: var(--ink-soft); cursor: pointer;
}
.prod-digital-current-delete:hover { border-color: #d33; color: #d33; background: #fdeceb; }
/* Un peu plus d'air entre la case "Ce produit est un fichier numérique...",
   son texte d'explication, le champ d'import et la carte "fichier
   actuellement enregistré" ci-dessus — sans toucher .twint-settings-card en
   général (réutilisée telle quelle par d'autres onglets/pages). */
.tab-panel-digital .twint-settings-card > * + * { margin-top: 14px; }
.form-actions { display: flex; gap: 12px; margin-top: 20px; }

/* ---------- Fiche produit à onglets (Général / Images / Prix & déclinaisons
   / Option supplémentaire / Poids & livraison) — CSS-only, pas de JS : les
   radios .product-tab-radio
   sont masquées visuellement mais restent des enfants directs de
   .product-tabs, au même niveau que <form> et .tab-panel-variants/
   .tab-panel-addons (les blocs déclinaisons et options supplémentaires après
   la fermeture du formulaire), donc le sélecteur de fratrie
   "radio:checked ~ ..." peut cibler aussi bien les panneaux à l'intérieur du
   <form> que ceux après sa fermeture. Pas de max-width ici :
   .product-tabs-nav (et donc son bouton "Enregistrer", voir
   .tabs-nav-actions) s'étend sur toute la largeur de la page — chaque carte
   de contenu (.listing-form, .variant-group-card...) porte son propre
   max-width. ---------- */
.product-tab-radio {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.product-tabs-nav {
  display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 16px; border-bottom: 1px solid var(--border);
}
.product-tabs-nav label {
  padding: 10px 16px; cursor: pointer; font-size: 13px; font-weight: 600; color: var(--ink-soft);
  border-bottom: 2px solid transparent; margin-bottom: -1px; transition: color .15s;
}
.product-tabs-nav label:hover { color: var(--ink); }
.tab-panel { display: none; }
.tab-panel-variants { display: none; margin-top: 20px; }
.tab-panel-addons { display: none; margin-top: 20px; }
#tab-general:checked ~ .product-tabs-nav label[for="tab-general"],
#tab-images:checked ~ .product-tabs-nav label[for="tab-images"],
#tab-price:checked ~ .product-tabs-nav label[for="tab-price"],
#tab-digital:checked ~ .product-tabs-nav label[for="tab-digital"],
#tab-addon:checked ~ .product-tabs-nav label[for="tab-addon"],
#tab-shipping:checked ~ .product-tabs-nav label[for="tab-shipping"] {
  color: var(--ink); border-bottom-color: var(--red);
}
#tab-general:checked ~ form .tab-panel-general { display: grid; }
#tab-images:checked ~ form .tab-panel-images { display: grid; }
#tab-price:checked ~ form .tab-panel-price { display: grid; }
#tab-price:checked ~ .tab-panel-variants { display: block; }
#tab-digital:checked ~ form .tab-panel-digital { display: block; }
#tab-addon:checked ~ .tab-panel-addons { display: block; }
#tab-shipping:checked ~ form .tab-panel-shipping { display: grid; }
/* Onglet "Option supplémentaire" : plus aucun champ ne vit dans le <form>
   principal (tout est géré ligne par ligne dans .tab-panel-addons, après sa
   fermeture — voir listingAddonsFormHtml()) — masquer seulement ses panneaux
   (comme pour les autres onglets ci-dessus) laisserait le cadre du <form>
   (.listing-form : fond, bordure, padding) flotter à vide au-dessus de
   "Options supplémentaires", avec pour seul contenu visible le bouton
   "Annuler". On masque donc le <form> ENTIER sur cet onglet (même principe
   que ".cfg-tabs > form" pour /admin/apparence, voir plus bas) : les autres
   onglets n'en sont pas affectés (chacun n'agit que sur son propre
   #tab-X:checked). */
#tab-addon:checked ~ form.listing-form { display: none; }
@media (max-width: 900px) {
  .product-tabs-nav { flex-direction: column; align-items: stretch; flex-wrap: nowrap; }
  .product-tabs-nav label { white-space: nowrap; }
}

/* ---------- Onglets génériques admin (ex: page Paiement — Carte/TWINT vs
   Virement bancaire) — même mécanique CSS-only que .product-tabs ci-dessus,
   mais sans dépendance à un <form> englobant : les panneaux sont de simples
   div, chacun pouvant contenir son propre formulaire indépendant. Pas de
   max-width ici, même raison que .product-tabs (voir plus haut). ---------- */
.admin-tab-radio {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.admin-tabs-nav {
  display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 16px; border-bottom: 1px solid var(--border);
}
.admin-tabs-nav label {
  padding: 10px 16px; cursor: pointer; font-size: 13px; font-weight: 600; color: var(--ink-soft);
  border-bottom: 2px solid transparent; margin-bottom: -1px; transition: color .15s;
}
.admin-tabs-nav label:hover { color: var(--ink); }
.admin-tab-panel { display: none; }
#tab-pay-card:checked ~ .admin-tabs-nav label[for="tab-pay-card"],
#tab-pay-bank:checked ~ .admin-tabs-nav label[for="tab-pay-bank"] {
  color: var(--ink); border-bottom-color: var(--red);
}
#tab-pay-card:checked ~ .admin-tab-panel-pay-card { display: block; }
#tab-pay-bank:checked ~ .admin-tab-panel-pay-bank { display: block; }
/* Un seul bouton "Enregistrer" visible à la fois dans .admin-tabs-nav, selon
   le sous-onglet actif — chacun form="" lié à son propre <form>
   indépendant (twint-mode-form / bank-transfer-form), voir twintFormHtml()
   dans routes/admin.php. */
.admin-nav-save-card, .admin-nav-save-bank { display: none; }
#tab-pay-card:checked ~ .admin-tabs-nav .admin-nav-save-card,
#tab-pay-bank:checked ~ .admin-tabs-nav .admin-nav-save-bank { display: inline-flex; }
@media (max-width: 900px) {
  .admin-tabs-nav { flex-direction: column; align-items: stretch; flex-wrap: nowrap; }
  .admin-tabs-nav label { white-space: nowrap; }
}

/* ---------- Page "Configuration" (/admin/apparence) à 6 onglets — même
   mécanique que .product-tabs ci-dessus (les radios .cfg-tab-radio sont des
   enfants directs de .cfg-tabs, au même niveau que <form> et les panneaux
   hors-form après sa fermeture) : "Mise en page" et "Mode vacances"
   partagent le même <form> (un seul POST quel que soit l'onglet visible) ;
   "Traductions" regroupe elle-même 3 anciens formulaires indépendants
   (accueil/avis/pied de page + Contact + Conditions générales) fusionnés en
   UN SEUL <form> (voir contactFieldsHtml()/conditionsFieldsHtml() dans
   routes/admin.php) pour n'avoir qu'un seul bouton "Enregistrer" ;
   "Livraison"/"Mot de passe admin" gardent chacun leur propre <form>
   indépendant ; "Paiements" gère son propre bouton via ses sous-onglets
   Carte/Banque (voir .admin-tabs-nav ci-dessus) plutôt que depuis
   .cfg-tabs-nav. Pas de max-width ici, même raison que .product-tabs (voir
   plus haut). ---------- */
.cfg-tab-radio {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.cfg-tabs-nav {
  display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 16px; border-bottom: 1px solid var(--border);
}
.cfg-tabs-nav label {
  padding: 10px 16px; cursor: pointer; font-size: 13px; font-weight: 600; color: var(--ink-soft);
  border-bottom: 2px solid transparent; margin-bottom: -1px; transition: color .15s;
}
.cfg-tabs-nav label:hover { color: var(--ink); }
.cfg-tab-panel { display: none; }
.cfg-tab-panel-translations,
.cfg-tab-panel-shipping,
.cfg-tab-panel-payments,
.cfg-tab-panel-facturation,
.cfg-tab-panel-password,
.cfg-tab-panel-email { display: none; margin-top: 20px; }
#tab-cfg-layout:checked ~ .cfg-tabs-nav label[for="tab-cfg-layout"],
#tab-cfg-vacation:checked ~ .cfg-tabs-nav label[for="tab-cfg-vacation"],
#tab-cfg-translations:checked ~ .cfg-tabs-nav label[for="tab-cfg-translations"],
#tab-cfg-shipping:checked ~ .cfg-tabs-nav label[for="tab-cfg-shipping"],
#tab-cfg-payments:checked ~ .cfg-tabs-nav label[for="tab-cfg-payments"],
#tab-cfg-facturation:checked ~ .cfg-tabs-nav label[for="tab-cfg-facturation"],
#tab-cfg-password:checked ~ .cfg-tabs-nav label[for="tab-cfg-password"],
#tab-cfg-email:checked ~ .cfg-tabs-nav label[for="tab-cfg-email"] {
  color: var(--ink); border-bottom-color: var(--red);
}
/* Le <form> partagé (Mise en page + Mode vacances) doit disparaître
   ENTIÈREMENT sur les autres onglets — masquer seulement ses panneaux
   laisserait son cadre (.listing-form : fond, bordure, padding) flotter, vide,
   au-dessus des cartes des autres onglets. */
.cfg-tabs > form { display: none; }
#tab-cfg-layout:checked ~ form,
#tab-cfg-vacation:checked ~ form { display: block; }
#tab-cfg-layout:checked ~ form .cfg-tab-panel-layout { display: block; }
#tab-cfg-vacation:checked ~ form .cfg-tab-panel-vacation { display: block; }
/* Un seul bouton "Enregistrer" visible à la fois dans .cfg-tabs-nav, selon
   l'onglet actif — chacun form="" lié à son propre <form> (voir
   adminAppearancePage() dans routes/admin.php). "Mise en page" et "Mode
   vacances" partagent le même <form> (cfg-layout-form) : leurs 2 boutons
   portent en plus chacun leur propre name=value ("onglet_actif") pour que
   adminAppearanceUpdateHandler() sache sur quel onglet rediriger après
   l'enregistrement, peu importe lequel des deux a été cliqué. "Paiements"
   n'a volontairement PAS de bouton ici : son bouton vit dans son propre
   .admin-tabs-nav imbriqué (Carte/Banque), voir plus haut. */
.cfg-save-btn-layout, .cfg-save-btn-vacation, .cfg-save-btn-translations,
.cfg-save-btn-shipping, .cfg-save-btn-facturation, .cfg-save-btn-password,
.cfg-save-btn-email { display: none; }
#tab-cfg-layout:checked ~ .cfg-tabs-nav .cfg-save-btn-layout,
#tab-cfg-vacation:checked ~ .cfg-tabs-nav .cfg-save-btn-vacation,
#tab-cfg-translations:checked ~ .cfg-tabs-nav .cfg-save-btn-translations,
#tab-cfg-shipping:checked ~ .cfg-tabs-nav .cfg-save-btn-shipping,
#tab-cfg-facturation:checked ~ .cfg-tabs-nav .cfg-save-btn-facturation,
#tab-cfg-password:checked ~ .cfg-tabs-nav .cfg-save-btn-password,
#tab-cfg-email:checked ~ .cfg-tabs-nav .cfg-save-btn-email { display: inline-flex; }
#tab-cfg-translations:checked ~ .cfg-tab-panel-translations { display: block; }
#tab-cfg-shipping:checked ~ .cfg-tab-panel-shipping { display: block; }
#tab-cfg-payments:checked ~ .cfg-tab-panel-payments { display: block; }
#tab-cfg-facturation:checked ~ .cfg-tab-panel-facturation { display: block; }
#tab-cfg-password:checked ~ .cfg-tab-panel-password { display: block; }
#tab-cfg-email:checked ~ .cfg-tab-panel-email { display: block; }
@media (max-width: 900px) {
  .cfg-tabs-nav { flex-direction: column; align-items: stretch; flex-wrap: nowrap; }
  .cfg-tabs-nav label { white-space: nowrap; }
}
/* Bouton(s) "Enregistrer" (.tabs-nav-actions) des 3 rangées d'onglets
   ci-dessus (.product-tabs-nav / .admin-tabs-nav / .cfg-tabs-nav) : sur
   desktop il vit tout à droite DANS la rangée d'onglets (margin-left:auto,
   voir plus haut) ; en dessous de 901px cette rangée devient scrollable
   horizontalement (voir .tabs-nav-scroll ci-dessus) et le bouton s'y
   retrouvait poussé hors champ, invisible sans swiper. On le sort donc de
   cette rangée scrollable pour en faire sa propre ligne pleine largeur,
   au-dessus des onglets (order:-1, alors que .tabs-nav-actions est le
   DERNIER élément dans le HTML — voir listingForm()/adminAppearancePage()/
   twintFormHtml() dans routes/admin.php), toujours visible sans avoir à
   toucher à rien. */
@media (max-width: 900px) {
  .tabs-nav-scroll {
    display: flex; flex-wrap: nowrap; gap: 4px;
    overflow-x: auto; -webkit-overflow-scrolling: touch;
  }
  .tabs-nav-actions {
    order: -1; width: 100%; margin-left: 0; padding-left: 0;
    padding-bottom: 12px; margin-bottom: 8px; border-bottom: 1px solid var(--border);
  }
  .tabs-nav-actions .btn { width: 100%; justify-content: center; }
}

.order-detail-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px; align-items: start; }
.order-detail-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; }
#order-status-form label.field-hint { display: block; margin-bottom: 10px; }
.order-field-input {
  display: block; width: 100%; margin-top: 4px; padding: 10px 12px; font-size: 14px;
  border-radius: 8px; border: 1px solid var(--border); box-sizing: border-box;
}
.order-divider { margin: 20px 0; border: none; border-top: 1px solid var(--border); }
.btn-delete-order { width: 100%; color: var(--red); border-color: var(--red); }

/* ---------- Déclinaisons produit (tailles, couleurs...) ---------- */
.variant-group-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 16px 18px; margin-bottom: 14px; max-width: 720px;
}
/* flex-wrap + align-items: flex-start (pas "center") : depuis que le nom du
   groupe est éditable ci-dessous (voir .variant-group-edit-form), cette
   ligne contient un formulaire potentiellement multi-lignes (onglets de
   langue) à côté du petit lien "Supprimer" — les centrer verticalement
   aurait décalé "Supprimer" au milieu du bloc d'onglets plutôt qu'en haut. */
.variant-group-head { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
.variant-group-edit-form { flex: 1 1 320px; }
.variant-group-head .link-btn { margin-top: 6px; }
.variant-option-list { list-style: none; margin: 0 0 12px; padding: 0; }
.variant-option-row {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px;
  padding: 9px 0; border-bottom: 1px dotted var(--border); font-size: 12.5px;
}
.variant-option-row:last-child { border-bottom: none; }
.variant-option-labels { color: var(--ink-soft); }
.variant-option-edit-form { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; flex: 1 1 auto; }
/* Onglets de langue (voir adminLangTabsHtml()) imbriqués dans ces
   formulaires : le <label> "Nom du groupe"/"Option" de chaque panneau porte
   lui-même le champ de largeur (flex-basis + min/max-width) plutôt que
   l'input, pour que celui-ci puisse rester en simple "width: 100%" — donner
   à l'input une largeur en "flex: 1 1 <n>px" alors que son parent <label>
   est un conteneur flex EN COLONNE (label au-dessus du champ) applique ce
   <n>px à la HAUTEUR plutôt qu'à la largeur (l'axe principal d'un flex en
   colonne est vertical), ce qui produisait un champ de texte démesurément
   haut — d'où ce découplage explicite largeur (sur le <label>, l'enfant
   direct du conteneur en ligne .variant-*-form) / hauteur naturelle (sur
   l'<input>, simple "width: 100%" de son <label>). */
.variant-option-edit-form label,
.variant-inline-form label,
.variant-group-edit-form label {
  display: flex; flex-direction: column; gap: 3px; font-size: 11.5px; color: var(--ink-soft);
  flex: 1 1 180px; min-width: 130px; max-width: 280px;
}
.variant-option-edit-form input[type="text"],
.variant-inline-form input[type="text"],
.variant-group-edit-form input[type="text"] {
  width: 100%; box-sizing: border-box; padding: 7px 9px; border: 1px solid var(--border);
  border-radius: 8px; font-size: 12.5px; font-family: inherit;
}
/* Rappel affiché sous les onglets de langue des formulaires de déclinaison
   (voir prod_variant_lang_hint et la demande de l'utilisateur : "ce n'est
   pas évident qu'il faut entrer le nom dans toutes les langues") —
   flex-basis 100% pour forcer sa propre ligne, sous les onglets et avant le
   bouton, plutôt que de se glisser à côté dans la même rangée flex. */
.variant-lang-hint { flex: 1 1 100%; margin: 2px 0 6px; }
.variant-option-num { display: flex; align-items: center; gap: 5px; font-size: 11.5px; color: var(--ink-soft); white-space: nowrap; }
.variant-option-num input {
  width: 80px; padding: 7px 8px; border: 1px solid var(--border); border-radius: 8px;
  font-size: 12.5px; font-family: inherit;
}
.variant-inline-form { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.variant-inline-form select {
  padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px; font-size: 13px; font-family: inherit;
}
.variant-combo-label { font-weight: 600; flex: 1 1 160px; }
.stock-edit-form { display: flex; align-items: center; gap: 8px; }
.stock-edit-form input[type="number"] {
  width: 90px; padding: 7px 9px; border: 1px solid var(--border); border-radius: 8px;
  font-size: 13px; font-family: inherit;
}

@media (max-width: 900px) {
  /* En dessous de 901px, .admin-sidebar et .admin-content passent d'une
     disposition en 2 COLONNES (une seule rangée implicite, voir la règle
     desktop de .admin-shell plus haut) à une disposition en 2 RANGÉES
     empilées (grid-template-columns à une seule colonne ci-dessous). Sans
     "align-content: start" ici, le "min-height: 100vh" hérité de la règle
     desktop reste actif : la grille distribue alors tout l'espace vertical
     en trop entre ces 2 rangées "auto" (comportement par défaut
     "align-content: normal" -> stretch sur une grille sans hauteur de
     rangée fixe) — sur une page courte, .admin-sidebar (qui ne contient que
     le logo + bouton burger, ou en plus le panneau déroulant ouvert) se
     retrouve étiré sur une bonne partie de l'écran, avec un grand bandeau
     sombre vide au-dessus du contenu de la page. */
  .admin-shell { grid-template-columns: minmax(0, 1fr); align-content: start; }
  /* En dessous de 901px, la barre latérale devient une barre du haut
     collante (logo + bouton "burger") : tout le reste (langue, nav, pied de
     page — voir .admin-sidebar-menu) passe dans un panneau déroulant caché
     par défaut, ouvert/fermé par ce bouton (même principe que le menu mobile
     du site public, .nav-toggle/.mobile-menu — voir wireMobileMenu() dans
     app.js, réutilisé ici avec #admin-nav-toggle/#admin-mobile-menu). Fini la
     rangée de 7 liens compressée en scroll horizontal illisible : chaque
     lien retrouve sa taille normale, en colonne, dans le panneau. */
  .admin-sidebar { position: sticky; top: 0; z-index: 10; padding: 12px 20px; }
  .admin-topbar-row { margin-bottom: 0; }
  .admin-nav-toggle { display: flex; }
  .admin-sidebar-menu { display: none; flex: none; }
  .admin-sidebar-menu.open {
    display: flex; margin-top: 14px; padding-top: 14px; border-top: 1px solid rgba(255,255,255,0.1);
  }
  .admin-content { padding: 20px; }
  .form-grid { grid-template-columns: 1fr; }
  /* Titre + bouton(s) d'action de l'en-tête de page (ex: "Produits" + "+
     Nouveau produit") : côte à côte sur desktop, mais peuvent ne plus tenir
     sur la même ligne une fois la page rétrécie — on les laisse passer à la
     ligne plutôt que de les comprimer ou déborder. */
  .admin-header { flex-wrap: wrap; row-gap: 10px; }
  .admin-header-actions { flex-wrap: wrap; }
  /* Onglets de langue FR/DE/EN (voir adminLangTabsHtml()) : utilisés dans de
     nombreuses cartes de l'onglet "Traductions" (bandeau, bloc d'accueil,
     avis, pied de page, CGV). Sur mobile, les 3 boutons + le lien "Copier FR
     → DE/EN" (poussé à droite via margin-left:auto) ne tiennent plus sur une
     seule ligne : on les laisse passer à la ligne plutôt que déborder. */
  .cgv-tab-buttons { flex-wrap: wrap; row-gap: 6px; }
  .cgv-copy-fr-btn { margin-left: 0; flex-basis: 100%; }
  /* Éditeur riche (bandeau, bloc d'accueil, CGV...) : la hauteur généreuse
     pensée pour desktop (jusqu'à 720px, voir .rte-content plus haut) laisse
     très peu de place à l'écran sur un téléphone une fois la barre d'outils
     affichée — le contenu reste de toute façon scrollable au-delà. */
  .rte-content { min-height: 220px; max-height: 50vh; }
  .rte-content-compact { min-height: 60px; max-height: 200px; }
}

/* ---------- Login ---------- */
.login-body { min-height: 100vh; display: flex; align-items: center; justify-content: center; background: var(--ink); }
.login-card { background: var(--surface); border-radius: var(--radius); padding: 36px; max-width: 380px; width: 100%; }
.login-card h1 { font-size: 20px; margin: 10px 0 20px; }
.login-card form { display: flex; flex-direction: column; gap: 14px; }
.login-card label { display: flex; flex-direction: column; gap: 6px; font-size: 13px; font-weight: 600; color: var(--ink-soft); }
.login-card input { padding: 11px; border: 1px solid var(--border); border-radius: 8px; font-size: 14px; }

/* ---------- 404 ---------- */
.error-page { text-align: center; padding: 90px 20px; }
.error-page h1 { font-size: 72px; color: var(--red); margin: 0; }
