:root {
  --v-900: #0a0413;
  --v-800: #120825;
  --v-700: #1b0f38;
  --v-600: #2a1553;
  --v-500: #4c1d95;
  --v-400: #7c3aed;
  --v-300: #a855f7;
  --v-200: #c084fc;
  --v-100: #e9d5ff;
  --accent: #d946ef;
  --ok: #4ade80;
  --warn: #fbbf24;
  --err: #fb7185;
  --text: #ede9fe;
  --muted: #a78bfa;
  --panel: rgba(24, 12, 46, 0.62);
  --border: rgba(168, 85, 247, 0.22);
  --glow: 0 0 40px rgba(124, 58, 237, 0.35);
  --radius: 18px;
  --shell: 1180px;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html { scroll-behavior: smooth; }
html, body { min-height: 100%; }

/* ------------------------------------------- preferences d'affichage

   Les reglages choisis dans Parametres sont poses en attributs sur <html> par
   prefs.js. Les regles ci-dessous n'ont plus qu'a en decouler.

   La taille passe par font-size sur <html> : tout le site etant dimensionne en
   px, on ne peut pas simplement changer un rem. On agit donc par zoom du texte
   via une variable appliquee au corps, ce qui evite de reecrire 5000 lignes. */
html[data-taille="s"]  body { font-size: 0.92em; }
html[data-taille="m"]  body { font-size: 1em; }
html[data-taille="l"]  body { font-size: 1.12em; }
html[data-taille="xl"] body { font-size: 1.26em; }

/* Le texte grossit, mais les elements dimensionnes en px ne suivent pas. On
   agrandit donc aussi les blocs de contenu les plus contraints, sans quoi les
   grandes tailles debordent de leurs cartes. */
html[data-taille="l"]  .btn,
html[data-taille="xl"] .btn { min-height: auto; padding-block: 0.7em; height: auto; }
html[data-taille="l"]  input,
html[data-taille="l"]  textarea,
html[data-taille="l"]  select,
html[data-taille="xl"] input,
html[data-taille="xl"] textarea,
html[data-taille="xl"] select { height: auto; padding-block: 0.65em; }

/* Espacement : agit sur les respirations internes des panneaux. */
html[data-densite="compact"] .panel,
html[data-densite="compact"] .card { padding: 15px 17px; }
html[data-densite="aere"] .panel,
html[data-densite="aere"] .card { padding: 32px 34px; }
html[data-densite="compact"] .views { gap: 26px; }
html[data-densite="aere"]    .views { gap: 64px; }

/* Lecture facilitee : on ne change pas de fonte (aucune police dyslexie n'est
   embarquee, et en charger une depuis un tiers ferait fuiter la visite), on
   agit sur l'espacement, qui est le facteur le plus determinant. */
html[data-lecture="oui"] body {
  letter-spacing: 0.035em;
  word-spacing: 0.09em;
  line-height: 1.85;
}
html[data-lecture="oui"] p,
html[data-lecture="oui"] li { max-width: 68ch; }

/* Contraste renforce : on remonte le texte secondaire, qui est le premier a
   devenir illisible sur ce fond sombre, et on affirme les bordures. */
html[data-contraste="fort"] {
  --text: #ffffff;
  --muted: #d8c8ff;
  --border: rgba(196, 132, 252, 0.55);
  --panel: rgba(24, 12, 46, 0.92);
}
html[data-contraste="fort"] .hint,
html[data-contraste="fort"] .lead,
html[data-contraste="fort"] .account-note { color: #cfc0f5; }
html[data-contraste="fort"] .btn { border-width: 2px; }

/* Animations coupees. La regle doit primer sur les nombreuses declarations
   locales : d'ou !important, exceptionnel mais justifie ici puisque l'intention
   de l'utilisateur prime sur l'esthetique de chaque composant. */
html[data-animations="non"] *,
html[data-animations="non"] *::before,
html[data-animations="non"] *::after {
  animation-duration: 0.001ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.001ms !important;
  scroll-behavior: auto !important;
}

/* Le decor anime en JavaScript ne repond pas aux regles ci-dessus : on le fige
   en le masquant, comme le fait deja la regle systeme. */
html[data-animations="non"] #particles { display: none; }
html[data-animations="non"] .aurora { animation: none; opacity: 0.5; }

/* --------------------------------------------- page Parametres

   Onglets. Meme principe que la barre de navigation : une pastille unique
   glisse sous l'onglet actif plutot que de dessiner un fond par bouton. */
.set-tabs {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin: 0 0 26px;
  padding: 5px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: rgba(18, 8, 37, 0.55);
}

.set-tab {
  position: relative;
  z-index: 1;
  padding: 9px 16px;
  border: 0;
  border-radius: 10px;
  background: none;
  color: var(--muted);
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: color 0.22s ease;
}

.set-tab:hover { color: var(--v-100); }
.set-tab.active { color: #fff; }

.set-tab-glider {
  position: absolute;
  top: 5px;
  left: 0;
  height: calc(100% - 10px);
  border-radius: 10px;
  background: linear-gradient(135deg, var(--v-500), var(--v-400));
  box-shadow: 0 6px 18px rgba(124, 58, 237, 0.4);
  /* Le script pose largeur et position ; la transition fait le reste. */
  transition: transform 0.42s cubic-bezier(0.22, 1, 0.36, 1),
              width 0.42s cubic-bezier(0.22, 1, 0.36, 1);
  pointer-events: none;
}

/* Tant que le script n'a pas mesure l'onglet actif, la pastille reste invisible
   plutot que de partir du coin gauche a la premiere ouverture. */
.set-tab-glider:not([style*="width"]) { opacity: 0; }

/* Les cartes d'un meme onglet respirent : sans cet ecart, deux panneaux
   successifs se touchent et paraissent n'en former qu'un. */
.set-pane {
  display: flex;
  flex-direction: column;
  gap: 18px;
}

/* La regle ci-dessus l'emporterait sur l'attribut « hidden » : les onglets
   inactifs resteraient affiches. On le retablit explicitement. */
.set-pane[hidden] { display: none; }

.set-card { display: flex; flex-direction: column; gap: 4px; }

/* Meme piege pour les cartes masquees, comme l'abonnement quand il n'y en a
   pas : « display: flex » annule « hidden » et laisse une carte vide. */
.set-card[hidden] { display: none; }

/* Une ligne = un reglage : le libelle a gauche, la commande a droite. */
.set-row,
.set-block {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 22px;
  padding: 17px 0;
  border-bottom: 1px solid rgba(168, 85, 247, 0.14);
}

.set-block { flex-direction: column; align-items: stretch; gap: 13px; }
.set-row:last-of-type,
.set-block:last-of-type { border-bottom: 0; }

.set-row-text { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.set-row-text b { font-size: 14px; color: var(--text); }
.set-row-text i {
  font-style: normal;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--muted);
}

.set-foot { padding-top: 18px; }

/* Interrupteur. Un bouton plutot qu'une case a cocher : l'etat est porte par
   aria-checked, ce qui permet de le styler librement tout en restant annonce
   correctement par les lecteurs d'ecran. */
.switch {
  position: relative;
  flex-shrink: 0;
  width: 50px;
  height: 28px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: rgba(10, 4, 19, 0.6);
  cursor: pointer;
  transition: background 0.25s ease, border-color 0.25s ease;
}

.switch-knob {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--muted);
  transition: transform 0.28s cubic-bezier(0.22, 1, 0.36, 1), background 0.25s ease;
}

.switch[aria-checked="true"] {
  background: linear-gradient(135deg, var(--v-500), var(--v-400));
  border-color: transparent;
}

.switch[aria-checked="true"] .switch-knob {
  transform: translateX(22px);
  background: #fff;
}

.switch:focus-visible { outline: 2px solid var(--v-300); outline-offset: 3px; }

/* Choix multiples (taille, densite) : boutons accoles facon segmented control. */
.set-choices {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 5px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: rgba(10, 4, 19, 0.45);
}

.set-choice {
  flex: 1 1 auto;
  padding: 9px 14px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--muted);
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease;
}

.set-choice:hover { color: var(--v-100); background: rgba(124, 58, 237, 0.14); }

.set-choice[aria-checked="true"] {
  color: #fff;
  background: linear-gradient(135deg, var(--v-500), var(--v-400));
  box-shadow: 0 4px 14px rgba(124, 58, 237, 0.35);
}

/* Langues : une carte par langue, drapeau en tete. */
.lang-list { display: flex; flex-direction: column; gap: 9px; padding-top: 6px; }

.lang-item {
  display: flex;
  align-items: center;
  gap: 13px;
  width: 100%;
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: rgba(10, 4, 19, 0.4);
  color: var(--text);
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.2s ease, background 0.2s ease, transform 0.2s ease;
}

.lang-item:hover {
  border-color: rgba(168, 85, 247, 0.5);
  background: rgba(124, 58, 237, 0.12);
}

.lang-item[aria-checked="true"] {
  border-color: var(--v-400);
  background: linear-gradient(135deg, rgba(76, 29, 149, 0.5), rgba(124, 58, 237, 0.3));
}

.lang-flag { font-size: 21px; line-height: 1; }

/* Coche de la langue active : poussee a droite, elle ne bouge pas le libelle. */
.lang-check { margin-left: auto; color: var(--v-200); opacity: 0; transition: opacity 0.2s ease; }
.lang-item[aria-checked="true"] .lang-check { opacity: 1; }
.lang-check svg { width: 18px; height: 18px; display: block; }

@media (max-width: 620px) {
  /* La commande passe sous son libelle : cote a cote, il ne reste que quelques
     caracteres par ligne pour l'explication. */
  .set-row { flex-direction: column; align-items: stretch; gap: 13px; }
  .set-row .btn,
  .set-row .switch { align-self: flex-start; }
  .set-tab { flex: 1 1 auto; text-align: center; }
}

body {
  font-family: "Outfit", system-ui, sans-serif;
  background: var(--v-900);
  color: var(--text);
  min-height: 100vh;
  overflow-x: hidden;
  position: relative;
  display: flex;
  flex-direction: column;
}

/* ---------------------------------------------------------------- decor */

/* Fixe et derriere le contenu (.views est en z-index 2) : la pluie doit rester
   identique sur toutes les vues et ne jamais intercepter les clics. */
#particles {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

/* L'animation du canvas est pilotee en JavaScript, pas par CSS : la regle
   globale qui coupe les animations ne doit donc pas la concerner. En revanche
   on garde le decor visible, sinon la page parait vide. */
@media (prefers-reduced-motion: reduce) {
  #particles { opacity: 0.55; }
}

/* Nappes de couleur animees : donnent la profondeur derriere les particules. */
.aurora {
  position: fixed;
  inset: -30%;
  z-index: -2;
  background:
    radial-gradient(38% 42% at 18% 22%, rgba(124, 58, 237, 0.55), transparent 62%),
    radial-gradient(34% 38% at 82% 26%, rgba(217, 70, 239, 0.42), transparent 64%),
    radial-gradient(46% 46% at 50% 88%, rgba(76, 29, 149, 0.55), transparent 66%);
  filter: blur(46px);
  animation: drift 24s ease-in-out infinite alternate;
  pointer-events: none;
}

@keyframes drift {
  0%   { transform: translate3d(0,0,0) scale(1); }
  50%  { transform: translate3d(2%, -3%, 0) scale(1.08); }
  100% { transform: translate3d(-2%, 3%, 0) scale(1.03); }
}

.grid-overlay {
  position: fixed;
  inset: 0;
  z-index: -1;
  background-image:
    linear-gradient(rgba(168, 85, 247, 0.055) 1px, transparent 1px),
    linear-gradient(90deg, rgba(168, 85, 247, 0.055) 1px, transparent 1px);
  background-size: 46px 46px;
  mask-image: radial-gradient(circle at 50% 40%, #000 20%, transparent 78%);
  -webkit-mask-image: radial-gradient(circle at 50% 40%, #000 20%, transparent 78%);
  pointer-events: none;
}

/* ---------------------------------------------------------------- header */

.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 16px 34px;
  background: rgba(10, 4, 19, 0.55);
  border-bottom: 1px solid var(--border);
  backdrop-filter: blur(18px);
}

/* Le bloc marque renvoie a l'accueil : curseur main et leger retour visuel
   pour signaler qu'il est cliquable. La transition ne porte que sur l'opacite,
   le .brand-mark ayant deja une animation sur filter. */
/* Conteneur porteur de la largeur. Le .brand garde sa taille naturelle a
   l'interieur : la zone cliquable et le contour de focus restent colles au
   logo au lieu de s'etirer sur toute la moitie de barre. */
.topbar-left { display: flex; align-items: center; flex: 1 1 0; }

.brand {
  display: flex;
  align-items: center;
  gap: 13px;
  cursor: pointer;
  user-select: none;
  transition: opacity 0.2s ease;
}

.brand:hover { opacity: 0.82; }

/* Focus visible au clavier, aligne sur le reste de l'interface. */
.brand:focus-visible {
  outline: 2px solid var(--v-300);
  outline-offset: 6px;
  border-radius: 10px;
}

/* Symbole detoure sur fond transparent : ni fond, ni border-radius. Le motif
   n'etant pas un disque plein, contain plutot que cover, sinon les extremites
   de la fleche seraient rognees. */
.brand-mark {
  display: block;
  width: 42px; height: 42px;
  flex: 0 0 auto;
  object-fit: contain;
  background: transparent;
  animation: pulse-mark 3.4s ease-in-out infinite;
}

/* drop-shadow et non box-shadow : box-shadow suit la boite rectangulaire de
   l'element et dessinerait un halo carre autour d'un symbole decoupe, alors
   que drop-shadow epouse les contours reels du dessin. */
@keyframes pulse-mark {
  0%, 100% { filter: drop-shadow(0 0 7px rgba(124, 58, 237, 0.55)); }
  50%      { filter: drop-shadow(0 0 14px rgba(217, 70, 239, 0.8)); }
}

.logo {
  font-size: 27px;
  font-weight: 900;
  letter-spacing: -0.02em;
  background: linear-gradient(92deg, var(--v-100), var(--v-300) 40%, var(--accent) 70%, var(--v-200));
  background-size: 260% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: shimmer 7s linear infinite;
}

@keyframes shimmer { to { background-position: 260% 0; } }

/* ------------------------------------------------------------ navigation */

.nav {
  position: relative;   /* repere de la pastille glissante */
  display: flex;
  gap: 4px;
  padding: 5px;
  border-radius: 999px;
  background: rgba(124, 58, 237, 0.1);
  border: 1px solid var(--border);
}

/* Pastille de l'onglet actif. Un seul element pour toute la barre : au lieu
   qu'un fond s'allume ici et s'eteigne la, il se deplace, ce qui rend le lien
   entre l'onglet quitte et l'onglet rejoint. Sa position et sa largeur sont
   posees par le script en variables. */
.nav-glider {
  position: absolute;
  top: 5px;                    /* aligne sur le padding de la barre */
  bottom: 5px;
  left: 0;
  width: var(--glider-w, 0);
  border-radius: 999px;
  /* Le magenta plein (--accent) ne portait le libelle blanc qu'a 3,5:1, sous le
     seuil de lisibilite. Cette extremite d'un cran plus sombre garde le meme
     balayage violet -> magenta en repassant au-dessus de 4,5:1. La variable
     globale reste intacte : ailleurs le degrade ne porte pas de texte. */
  background: linear-gradient(135deg, var(--v-400), #c026d3);
  box-shadow: 0 4px 18px rgba(124, 58, 237, 0.45);
  /* Deplacement et etirement reunis dans un seul transform. Les proprietes
     separees translate/scale auraient permis deux durees distinctes, mais
     `scale` reste sans effet sur cet element (verifie : la geometrie ne bouge
     pas, la ou scaleX dans transform l'etire bien). On garde donc transform,
     dont le rendu est fiable, et le script echelonne les deux temps.
     Compose par le GPU : pas de reflow a chaque image. */
  transform: translateX(var(--glider-x, 0)) scaleX(var(--glider-s, 1));
  /* L'etirement part du bord arriere, sinon la pastille grandit des deux cotes
     et mord sur l'onglet qu'elle vient de quitter. */
  transform-origin: var(--glider-o, 50%) 50%;
  /* Ressort leger : la pastille depasse d'un cheveu puis revient, ce qui donne
     l'elan d'un objet lance plutot qu'un deplacement mecanique.
     Le depassement d'une courbe de Bezier est proportionnel a la distance : a
     1.42, un saut d'un bout a l'autre de la barre sortait de 30px hors du
     cadre et empietait sur le logo. A 1.18 l'elan reste lisible entre onglets
     voisins sans deborder sur les longs trajets. */
  transition: transform 0.42s cubic-bezier(0.32, 1.18, 0.44, 1),
              width 0.42s cubic-bezier(0.32, 1.18, 0.44, 1),
              box-shadow 0.42s ease;
  pointer-events: none;        /* la pastille ne doit pas voler les clics */
}

/* Pendant le trajet, la trainee s'allonge et s'intensifie : la lumiere sous la
   pastille suit son elan au lieu de rester une ombre fixe. */
.nav-glider.file {
  box-shadow: 0 4px 26px rgba(124, 58, 237, 0.6), 0 0 40px rgba(217, 70, 239, 0.28);
}

/* Premier rendu : la pastille se pose sans glisser depuis le bord gauche. Le
   script retire cette classe juste apres avoir mesure. */
.nav-glider.no-anim { transition: none; }

/* Invisible tant que le script ne l'a pas mesuree. Sans cela, elle s'affiche
   une image a sa largeur par defaut, au mauvais endroit, avant d'etre placee.
   Le script leve ce voile des le premier placement. */
.nav-glider:not(.pret) { opacity: 0; }

.nav-link {
  position: relative;   /* passe au-dessus de la pastille */
  z-index: 1;
  padding: 8px 18px;
  border: none;
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 500;
  cursor: pointer;
  transition: color 0.18s;
}

.nav-link:hover { color: var(--v-100); }

/* Le fond est desormais porte par la pastille glissante : l'onglet actif ne
   garde que sa couleur de texte.
   La transition est plus lente a l'allumage qu'a l'extinction : le libelle
   quitte est rendu au gris des que la pastille s'en va, tandis que le libelle
   vise ne blanchit qu'a mesure qu'elle le recouvre. Sans ce decalage, les deux
   onglets paraissent actifs en meme temps pendant le trajet. */
.nav-link.active {
  color: #fff;
  transition: color 0.34s ease 0.08s;
}

/* Mouvement reduit : la pastille change d'onglet sans trajet ni deformation. */
@media (prefers-reduced-motion: reduce) {
  .nav-glider {
    transition: none;
    transform: translateX(var(--glider-x, 0));   /* sans etirement */
  }
  .nav-glider.file { box-shadow: 0 4px 18px rgba(124, 58, 237, 0.45); }
}

/* La barre est en space-between sur trois zones. La zone droite ne contient
   plus que la pastille utilisateur, masquee tant que le profil n'est pas
   charge : sans largeur de base, la navigation se decalerait vers la droite
   puis sauterait a l'arrivee du profil. flex: 1 sur les deux zones laterales
   leur donne la meme part, ce qui centre la navigation de facon stable.
   (le flex de .brand est pose plus haut, dans sa declaration principale) */
.topbar-right {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
  flex: 1 1 0;
}

/* ---------------------------------------------------------------- panier */

.cart-btn {
  position: relative;
  flex: 0 0 auto;
  width: 40px; height: 40px;
  display: grid;
  place-items: center;
  border-radius: 12px;
  background: var(--panel);
  border: 1px solid var(--border);
  backdrop-filter: blur(14px);
  color: var(--v-200);
  cursor: pointer;
  transition: color 0.18s, border-color 0.18s, background 0.18s, transform 0.18s;
}

.cart-btn svg { width: 20px; height: 20px; }

.cart-btn:hover {
  color: #fff;
  border-color: var(--v-300);
  background: rgba(124, 58, 237, 0.22);
  transform: translateY(-1px);
  box-shadow: 0 8px 24px rgba(124, 58, 237, 0.4);
}

/* L'anse du panier se souleve legerement au survol, comme si on s'appretait
   a le prendre. Le detail est minuscule mais rend l'icone vivante. */
.cart-btn svg path { transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1); transform-origin: center; }
.cart-btn:hover svg path { transform: translateY(-1.5px); }

/* Panier garni : la teinte passe au violet plein pour distinguer d'un coup
   d'oeil un panier rempli d'un panier vide. */
.cart-btn.filled {
  color: var(--v-100);
  border-color: var(--v-400);
  background: rgba(124, 58, 237, 0.18);
}

/* Halo lent autour d'un panier rempli : rappelle discretement qu'une
   selection attend, sans clignoter.
   Pas de z-index negatif ici : le halo passerait derriere le fond translucide
   de la barre du haut, qui l'effacerait. Il reste donc au-dessus du bouton,
   avec une opacite faible pour ne pas voiler l'icone. */
.cart-btn.filled::after {
  content: "";
  position: absolute;
  inset: -4px;
  border-radius: 15px;
  box-shadow: 0 0 16px 2px rgba(217, 70, 239, 0.55);
  animation: cart-halo 3.4s ease-in-out infinite;
  pointer-events: none;
}

/* L'icone doit rester au-dessus du halo, sinon elle parait voilee. */
.cart-btn svg, .cart-count { position: relative; z-index: 1; }

@keyframes cart-halo {
  0%, 100% { opacity: 0.35; }
  50%      { opacity: 0.85; }
}

.cart-count {
  position: absolute;
  top: -5px; right: -5px;
  min-width: 19px;
  height: 19px;
  padding: 0 5px;
  display: grid;
  place-items: center;
  border-radius: 999px;
  background: linear-gradient(135deg, var(--v-400), var(--accent));
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 3px 12px rgba(217, 70, 239, 0.6);
}

/* display: grid ci-dessus l'emporterait sur l'attribut hidden : sans cette
   regle, un panier vide afficherait quand meme une pastille a zero. */
.cart-count[hidden] { display: none; }

/* Secousse jouee a chaque ajout : le bouton est loin de la carte cliquee,
   ce signal relie le geste a son effet. */
.cart-btn.pulse { animation: cart-pulse 0.6s cubic-bezier(0.34, 1.56, 0.64, 1); }

@keyframes cart-pulse {
  0%   { transform: scale(1) rotate(0deg); }
  25%  { transform: scale(1.22) rotate(-7deg); }
  50%  { transform: scale(0.94) rotate(5deg); }
  75%  { transform: scale(1.08) rotate(-2deg); }
  100% { transform: scale(1) rotate(0deg); }
}

/* Onde qui s'echappe du bouton a l'ajout : le regard part du panier et
   comprend qu'il vient de recevoir quelque chose.
   opacity: 0 par defaut, car une animation sans fill-mode rend son element au
   style de base une fois terminee : sans cela l'anneau resterait affiche en
   bordure magenta pleine autour du bouton. */
.cart-btn.pulse::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 12px;
  border: 2px solid var(--accent);
  opacity: 0;
  animation: cart-ring 0.6s ease-out;
  pointer-events: none;
}

@keyframes cart-ring {
  from { opacity: 0.85; transform: scale(1); }
  to   { opacity: 0; transform: scale(1.9); }
}

/* La pastille rebondit a chaque changement de valeur. La classe est posee par
   le script, car l'element n'est jamais recree : sans cela l'animation ne
   serait jouee qu'au premier affichage de la page. */
.cart-count.bump { animation: cart-count-in 0.42s cubic-bezier(0.34, 1.56, 0.64, 1); }

@keyframes cart-count-in {
  from { opacity: 0; transform: scale(0.2); }
  to   { opacity: 1; transform: scale(1); }
}

.cart-backdrop {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  justify-content: flex-end;
  background: rgba(6, 2, 12, 0.66);
  backdrop-filter: blur(6px);
  animation: fade-in 0.2s ease;
}

.cart-backdrop[hidden] { display: none; }

.cart-sheet {
  position: relative;
  overflow: hidden auto;
  width: min(420px, 100%);
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 22px 24px 24px;
  background: linear-gradient(170deg, rgba(32, 16, 60, 0.97), rgba(14, 6, 28, 0.98));
  border-left: 1px solid var(--v-500);
  box-shadow: -30px 0 80px rgba(0, 0, 0, 0.5);
  /* Le ressort leger (overshoot) donne du poids au panneau : il arrive et se
     cale, au lieu de glisser mollement jusqu'a sa position. */
  animation: cart-in 0.46s cubic-bezier(0.22, 1, 0.36, 1);
}

@keyframes cart-in {
  from { transform: translateX(100%); }
  to   { transform: translateX(0); }
}

/* Filet lumineux sur le bord gauche : marque la limite du panneau et lui
   donne le meme trait que les autres surfaces vitrees de l'interface. */
.cart-sheet::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 1px;
  background: linear-gradient(180deg, transparent, var(--accent) 30%, var(--v-300) 60%, transparent);
  animation: cart-edge 0.9s ease-out backwards;
  animation-delay: 0.2s;
  pointer-events: none;
}

@keyframes cart-edge {
  from { opacity: 0; transform: scaleY(0.3); }
  to   { opacity: 1; transform: scaleY(1); }
}

/* Halo qui respire en haut du panneau : donne de la profondeur et maintient
   la surface vivante, a un rythme assez lent pour ne pas fatiguer. */
.cart-glow {
  position: absolute;
  top: -120px; right: -80px;
  width: 320px; height: 320px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(168, 85, 247, 0.34), transparent 68%);
  animation: cart-glow-breathe 7s ease-in-out infinite;
  pointer-events: none;
}

@keyframes cart-glow-breathe {
  0%, 100% { opacity: 0.6; transform: scale(1); }
  50%      { opacity: 1; transform: scale(1.15); }
}

.cart-head {
  position: relative;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
}

/* L'en-tete et le pied entrent apres le panneau, decales : le regard suit
   l'ordre de lecture au lieu de tout recevoir d'un bloc. */
.cart-head, .cart-foot, .cart-empty {
  animation: cart-fade-up 0.5s cubic-bezier(0.22, 1, 0.36, 1) backwards;
}

.cart-head  { animation-delay: 0.1s; }
.cart-empty { animation-delay: 0.16s; }
.cart-foot  { animation-delay: 0.3s; }

@keyframes cart-fade-up {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}

.cart-head .eyebrow { margin-bottom: 8px; }

.cart-head h3 {
  font-size: 21px;
  font-weight: 900;
  letter-spacing: -0.02em;
  background: linear-gradient(92deg, var(--v-100), var(--v-300) 45%, var(--accent) 75%, var(--v-200));
  background-size: 260% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: shimmer 7s linear infinite;
}
/* La croix du panier suit le flux de l'en-tete plutot que le coin absolu
   herite de .modal-close, qui la superposerait au titre. */
.cart-head .modal-close { position: static; }

.cart-items { display: flex; flex-direction: column; gap: 10px; }

.cart-item {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 14px 16px;
  border-radius: 14px;
  background: linear-gradient(135deg, rgba(124, 58, 237, 0.2), rgba(27, 15, 56, 0.5));
  border: 1px solid var(--border);
  /* Apparition en cascade : chaque ligne entre avec un leger retard, ce qui
     donne le sens de lecture du haut vers le bas. */
  animation: cart-item-in 0.5s cubic-bezier(0.22, 1, 0.36, 1) backwards;
  animation-delay: calc(var(--i, 0) * 80ms);
  transition: border-color 0.22s, background 0.22s, transform 0.22s, box-shadow 0.22s;
}

@keyframes cart-item-in {
  from { opacity: 0; transform: translateX(34px) scale(0.96); }
  to   { opacity: 1; transform: translateX(0) scale(1); }
}

.cart-item:hover {
  border-color: var(--v-300);
  transform: translateX(-3px);
  box-shadow: -6px 0 22px rgba(124, 58, 237, 0.3);
}

/* Lueur qui traverse la ligne au survol. Elle part du bord gauche et file
   vers la droite, ce qui souligne la ligne pointee sans la deplacer. */
.cart-item::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 30%, rgba(217, 70, 239, 0.22) 50%, transparent 70%);
  transform: translateX(-100%);
  opacity: 0;
  pointer-events: none;
}

.cart-item:hover::after { animation: cart-item-sweep 0.75s ease-out; }

@keyframes cart-item-sweep {
  from { opacity: 1; transform: translateX(-100%); }
  to   { opacity: 0; transform: translateX(100%); }
}

/* Liseré vertical sur la formule facturee : c'est la seule qui coute, elle
   merite un repere visuel que les lignes absorbees n'ont pas. */
.cart-item:not(.included)::before {
  content: "";
  position: absolute;
  left: 0; top: 12%; bottom: 12%;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: linear-gradient(180deg, var(--v-300), var(--accent));
  box-shadow: 0 0 12px rgba(217, 70, 239, 0.7);
}

/* Ligne absorbee par un palier superieur : volontairement en retrait, elle
   informe sans se disputer l'attention avec la formule reellement facturee. */
.cart-item.included {
  background: rgba(10, 4, 19, 0.34);
  opacity: 0.66;
  transition: opacity 0.22s, border-color 0.22s, transform 0.22s;
}

/* Au survol elle redevient lisible : le client peut vouloir la relire avant
   de decider de la retirer. */
.cart-item.included:hover { opacity: 1; box-shadow: none; }

.cart-item-main { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.cart-item-main b { font-size: 15px; font-weight: 700; }
.cart-item-main span { font-size: 11.5px; color: var(--muted); }

.cart-item-side { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; }

.cart-item-price {
  font-size: 15px;
  font-weight: 900;
  color: var(--v-100);
  font-variant-numeric: tabular-nums;
  text-shadow: 0 0 16px rgba(217, 70, 239, 0.5);
}

.cart-item-side s { font-size: 12.5px; color: rgba(167, 139, 250, 0.5); }

.cart-tag {
  padding: 3px 9px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ok);
  background: rgba(74, 222, 128, 0.14);
  border: 1px solid rgba(74, 222, 128, 0.35);
  animation: cart-tag-in 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) backwards;
  animation-delay: calc(var(--i, 0) * 80ms + 160ms);
}

@keyframes cart-tag-in {
  from { opacity: 0; transform: scale(0.6); }
  to   { opacity: 1; transform: scale(1); }
}

.cart-remove {
  position: relative;
  z-index: 1;
  width: 24px; height: 24px;
  display: grid;
  place-items: center;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--muted);
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
  transition: color 0.18s, background 0.18s, transform 0.18s;
}

.cart-remove:hover {
  color: #fff;
  background: rgba(251, 113, 133, 0.28);
  transform: scale(1.15);
}

.cart-remove:active { transform: scale(0.9); }

.cart-empty {
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--muted);
}

.cart-empty[hidden] { display: none; }

/* Le pied colle en bas du panneau : le total et l'action restent visibles
   meme quand la liste s'allonge et defile. */
.cart-foot {
  margin-top: auto;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}

.cart-total { display: flex; align-items: baseline; justify-content: space-between; }
.cart-total span { font-size: 12.5px; color: var(--muted); }

/* Le degrade du total defile lentement, comme le logo et le titre de la page
   d'accueil : c'est le chiffre qui compte, il porte donc le meme traitement. */
.cart-total b {
  font-size: 26px;
  font-weight: 900;
  letter-spacing: -0.02em;
  background: linear-gradient(96deg, var(--v-100), var(--accent) 60%, var(--v-200));
  background-size: 260% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  font-variant-numeric: tabular-nums;
  animation: shimmer 6s linear infinite;
}

/* Le bouton de paiement respire : c'est l'action attendue du panneau, elle
   doit se distinguer du reste sans clignoter. */
#cart-checkout {
  position: relative;
  overflow: hidden;
  animation: cart-cta-breathe 3.6s ease-in-out infinite;
}

@keyframes cart-cta-breathe {
  0%, 100% { box-shadow: 0 6px 22px rgba(124, 58, 237, 0.42); }
  50%      { box-shadow: 0 8px 32px rgba(217, 70, 239, 0.66); }
}

/* Lueur qui balaie le bouton par intermittence, comme sur le bloc portefeuille
   de l'ecran de paiement.
   ::before et non ::after : .btn::after porte deja le reflet joue au survol,
   le remplacer ici supprimerait cette reaction au passage de la souris. */
#cart-checkout::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 30%, rgba(255, 255, 255, 0.26) 50%, transparent 70%);
  transform: translateX(-100%);
  animation: cart-cta-sweep 4.5s ease-in-out infinite;
  pointer-events: none;
}

@keyframes cart-cta-sweep {
  0%, 55% { transform: translateX(-100%); }
  100%    { transform: translateX(100%); }
}

/* Un bouton desactive (panier vide) ne doit ni respirer ni scintiller : ces
   effets promettent une action qui n'aboutirait pas. */
#cart-checkout:disabled { animation: none; box-shadow: none; }
#cart-checkout:disabled::before { animation: none; opacity: 0; }

.cart-clear {
  border: none;
  background: transparent;
  color: var(--muted);
  font-family: inherit;
  font-size: 12.5px;
  cursor: pointer;
  transition: color 0.18s;
}

.cart-clear:hover { color: var(--err); }
.cart-clear[hidden] { display: none; }

@media (max-width: 520px) {
  .cart-sheet { width: 100%; }
}

.status-pill {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 8px 15px;
  border-radius: 999px;
  font-size: 13px;
  color: var(--muted);
  background: var(--panel);
  border: 1px solid var(--border);
  backdrop-filter: blur(14px);
  white-space: nowrap;
}

.status-pill .dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--warn);
  box-shadow: 0 0 12px currentColor;
  animation: blink 1.6s ease-in-out infinite;
}

.status-pill.online .dot { background: var(--ok); animation: none; }
.status-pill.offline .dot { background: var(--err); }

@keyframes blink { 50% { opacity: 0.3; } }

/* ---------------------------------------------------------------- vues */

.views {
  position: relative;
  z-index: 2;
  flex: 1;
  width: 100%;
  max-width: var(--shell);
  /* Marge gauche plus large : la pluie de "+" occupe cette bande. */
  margin: 0 auto;
  padding: 46px 34px 60px;
}

.view { display: none; }

.view.active {
  display: block;
  animation: view-in 0.5s cubic-bezier(0.2, 0.8, 0.2, 1) both;
}

@keyframes view-in {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: none; }
}

.eyebrow {
  display: inline-block;
  padding: 6px 14px;
  border-radius: 999px;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--v-200);
  background: rgba(124, 58, 237, 0.16);
  border: 1px solid var(--border);
  margin-bottom: 18px;
}

.section-head { margin-bottom: 30px; }

.section-head h2 {
  font-size: 34px;
  font-weight: 900;
  letter-spacing: -0.02em;
  margin-bottom: 10px;
}

.section-head p { color: var(--muted); max-width: 620px; line-height: 1.65; }

/* --------------------------------------------------------------- accueil */

.hero { padding: 26px 0 54px; max-width: 760px; }

.hero-title {
  font-size: clamp(38px, 5.6vw, 64px);
  line-height: 1.06;
  font-weight: 900;
  letter-spacing: -0.035em;
  margin-bottom: 22px;
}

.grad {
  background: linear-gradient(96deg, var(--v-300), var(--accent) 55%, var(--v-200));
  background-size: 220% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: shimmer 6s linear infinite;
}

.hero-sub {
  font-size: 17.5px;
  line-height: 1.72;
  color: var(--muted);
  max-width: 600px;
  margin-bottom: 32px;
}

/* La marge basse servait a ecarter les compteurs, retires depuis : les
   garanties qui suivent portent desormais leur propre espacement. */
.hero-actions { display: flex; flex-wrap: wrap; gap: 13px; }

/* --------------------------------------------------------------- cartes */

/* ===================================================== accueil : vente */

/* Trois garanties sous les boutons. La coche est dessinee en CSS plutot
   qu'ecrite dans le HTML : elle est decorative et n'a rien a faire dans le
   texte lu par un lecteur d'ecran. */
/* Derniere ligne du hero : le trait la separe des blocs de vente qui suivent,
   role que tenait auparavant la bordure des compteurs. */
.hero-reassure {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 26px;
  margin: 34px 0 0;
  padding: 26px 0 0;
  border-top: 1px solid var(--border);
  list-style: none;
}

.hero-reassure li {
  position: relative;
  padding-left: 24px;
  font-size: 13.5px;
  color: var(--v-200);
}

.hero-reassure li::before {
  content: "";
  position: absolute;
  left: 4px;
  top: 6px;
  width: 6px;
  height: 10px;
  border: solid #86efac;
  border-width: 0 2px 2px 0;
  transform: rotate(42deg);
}

/* Avant / apres. Les deux colonnes ont le meme poids visuel : la comparaison
   doit se lire d'un coup d'oeil, sans que l'une ecrase l'autre. */
.home-split {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 18px;
  margin-top: 54px;
}

.split-col {
  padding: 26px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--panel);
  backdrop-filter: blur(18px);
}

.split-bad {
  border-color: rgba(148, 163, 184, 0.22);
  background: rgba(15, 12, 24, 0.55);
}

.split-good {
  border-color: rgba(168, 85, 247, 0.34);
  box-shadow: 0 18px 50px rgba(124, 58, 237, 0.16);
}

.split-tag {
  display: inline-block;
  margin-bottom: 16px;
  padding: 5px 12px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.split-bad .split-tag {
  color: #cbd5e1;
  background: rgba(148, 163, 184, 0.14);
  border: 1px solid rgba(148, 163, 184, 0.28);
}

.split-good .split-tag {
  color: var(--v-100);
  background: rgba(168, 85, 247, 0.18);
  border: 1px solid rgba(168, 85, 247, 0.4);
}

.split-col ul { margin: 0; padding: 0; list-style: none; }

.split-col li {
  position: relative;
  padding: 9px 0 9px 26px;
  font-size: 14px;
  line-height: 1.55;
  color: var(--v-200);
}

.split-bad li { color: #94a3b8; }

/* Croix pour le constat, coche pour la solution : le signe porte le sens,
   la couleur seule ne suffirait pas a distinguer les deux colonnes. */
.split-bad li::before {
  content: "×";
  position: absolute;
  left: 4px;
  top: 8px;
  font-size: 16px;
  color: #94a3b8;
}

.split-good li::before {
  content: "";
  position: absolute;
  left: 6px;
  top: 14px;
  width: 6px;
  height: 10px;
  border: solid #86efac;
  border-width: 0 2px 2px 0;
  transform: rotate(42deg);
}

.home-plans-head { margin-top: 64px; }

.home-plans {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 18px;
  align-items: start;
}

.home-plan {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 28px 24px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--panel);
  backdrop-filter: blur(18px);
  transition: transform 0.22s ease, border-color 0.22s ease, box-shadow 0.22s ease;
}

.home-plan:hover {
  transform: translateY(-4px);
  border-color: rgba(196, 132, 252, 0.5);
  box-shadow: 0 22px 54px rgba(124, 58, 237, 0.26);
}

/* La formule mise en avant se distingue par un liisere et une legere avance,
   pas par une taille differente : les trois colonnes restent alignees. */
.home-plan.featured {
  border-color: rgba(217, 70, 239, 0.55);
  box-shadow: 0 22px 60px rgba(217, 70, 239, 0.22);
  background: linear-gradient(165deg, rgba(46, 16, 74, 0.8), rgba(20, 8, 38, 0.85));
}

.home-plan-flag {
  position: absolute;
  top: -11px;
  left: 24px;
  padding: 4px 12px;
  border-radius: 999px;
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #fff;
  background: linear-gradient(120deg, #a855f7, #d946ef);
  box-shadow: 0 6px 18px rgba(217, 70, 239, 0.45);
}

.home-plan h3 {
  font-size: 21px;
  font-weight: 900;
  letter-spacing: -0.02em;
  color: var(--v-100);
}

.home-plan-line {
  margin-top: 4px;
  font-size: 13px;
  color: var(--v-300);
}

.home-plan-price {
  display: flex;
  align-items: baseline;
  gap: 5px;
  margin: 18px 0 4px;
  padding-bottom: 18px;
  border-bottom: 1px solid rgba(168, 85, 247, 0.16);
}

.home-plan-price b {
  font-size: 38px;
  font-weight: 900;
  letter-spacing: -0.04em;
  color: #fff;
}

.home-plan-price span { font-size: 14px; color: var(--v-300); }

.home-plan ul {
  flex: 1;
  margin: 16px 0 22px;
  padding: 0;
  list-style: none;
}

.home-plan li {
  position: relative;
  padding: 8px 0 8px 24px;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--v-200);
}

.home-plan li::before {
  content: "";
  position: absolute;
  left: 4px;
  top: 13px;
  width: 5px;
  height: 9px;
  border: solid var(--v-400);
  border-width: 0 2px 2px 0;
  transform: rotate(42deg);
}

.home-plan.featured li::before { border-color: #d946ef; }

.home-plans-note {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 12px;
  margin-top: 22px;
  font-size: 13.5px;
  color: var(--v-300);
}

/* Rappel final : dernier point de sortie de la page, donc dernier endroit ou
   proposer l'achat avant que le visiteur reparte. */
.home-final {
  margin-top: 70px;
  padding: 54px 32px;
  text-align: center;
  border-radius: var(--radius);
  border: 1px solid rgba(168, 85, 247, 0.34);
  background:
    radial-gradient(ellipse at 50% 0%, rgba(217, 70, 239, 0.18), transparent 62%),
    linear-gradient(165deg, rgba(40, 14, 68, 0.72), rgba(16, 6, 32, 0.86));
  box-shadow: 0 26px 70px rgba(124, 58, 237, 0.2);
}

.home-final h2 {
  font-size: clamp(26px, 3.4vw, 40px);
  font-weight: 900;
  letter-spacing: -0.03em;
  color: #fff;
}

.home-final p {
  max-width: 560px;
  margin: 14px auto 0;
  font-size: 15px;
  line-height: 1.65;
  color: var(--v-200);
}

.home-final-actions {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  margin-top: 28px;
}

.home-final-note { font-size: 12.5px; color: var(--v-400); }

@media (max-width: 960px) {
  .home-split { grid-template-columns: 1fr; }
  .home-plans { grid-template-columns: 1fr; }
  /* Sans marge, la pastille « le plus choisi » chevaucherait la carte
     precedente une fois les colonnes empilees. */
  .home-plan.featured { margin-top: 12px; }
}

.cards-3, .cards-2 { display: grid; gap: 18px; }
.cards-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.cards-2 { grid-template-columns: repeat(3, minmax(0, 1fr)); }

@media (max-width: 960px) {
  .cards-3, .cards-2 { grid-template-columns: 1fr; }
}

.card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 24px;
  backdrop-filter: blur(18px);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.4);
  position: relative;
  overflow: hidden;
  transition: background 0.22s, border-color 0.22s, box-shadow 0.22s;
}

/* Survol sans deplacement : la carte n'est pas cliquable, la soulever la
   faisait passer pour un bouton. Seule la couleur reagit. */
.card:hover {
  border-color: var(--v-300);
  background: rgba(124, 58, 237, 0.14);
  box-shadow: 0 20px 54px rgba(124, 58, 237, 0.22);
}

/* Lisere lumineux en haut de chaque carte. */
.card::before {
  content: "";
  position: absolute;
  top: 0; left: 12%;
  width: 76%; height: 1px;
  background: linear-gradient(90deg, transparent, var(--v-300), transparent);
  opacity: 0.7;
  transition: opacity 0.22s;
}

/* Le lisere s'intensifie au survol : c'est ce qui remplace le mouvement. */
.card:hover::before { opacity: 1; }

.card-icon {
  display: grid;
  place-items: center;
  width: 42px; height: 42px;
  margin-bottom: 16px;
  border-radius: 12px;
  font-size: 17px;
  font-weight: 900;
  color: var(--v-100);
  background: rgba(124, 58, 237, 0.22);
  border: 1px solid var(--border);
}

.card h3 { font-size: 17px; font-weight: 700; margin-bottom: 9px; }
.card p { font-size: 14px; line-height: 1.68; color: var(--muted); }

/* ---------------------------------------------------------------- etapes */

.steps { list-style: none; display: flex; flex-direction: column; gap: 14px; margin-bottom: 40px; }

.step {
  display: flex;
  gap: 18px;
  align-items: flex-start;
  padding: 22px 24px;
  border-radius: var(--radius);
  background: var(--panel);
  border: 1px solid var(--border);
  backdrop-filter: blur(18px);
  transition: transform 0.2s, border-color 0.2s;
}

.step:hover { transform: translateX(5px); border-color: var(--v-300); }

.step-num {
  display: grid;
  place-items: center;
  flex-shrink: 0;
  width: 38px; height: 38px;
  border-radius: 50%;
  font-weight: 900;
  color: #fff;
  background: linear-gradient(135deg, var(--v-400), var(--accent));
  box-shadow: 0 4px 18px rgba(124, 58, 237, 0.45);
}

.step h3 { font-size: 16px; margin-bottom: 6px; }
.step p { font-size: 14px; line-height: 1.68; color: var(--muted); }

code {
  font-family: "JetBrains Mono", monospace;
  font-size: 12.5px;
  padding: 2px 7px;
  border-radius: 6px;
  background: rgba(10, 4, 19, 0.7);
  border: 1px solid var(--border);
  color: var(--v-200);
}

.cta {
  text-align: center;
  padding: 42px 28px;
  border-radius: var(--radius);
  background: linear-gradient(135deg, rgba(124, 58, 237, 0.22), rgba(217, 70, 239, 0.16));
  border: 1px solid var(--v-500);
  backdrop-filter: blur(18px);
}

.cta h3 { font-size: 24px; font-weight: 900; margin-bottom: 8px; }
.cta p { color: var(--muted); margin-bottom: 22px; }

/* --------------------------------------------------------------- panneau */

/* Le panneau travaille en deux colonnes de roles distincts : a gauche ce que
   l'on prepare (serveur, message, compteurs), a droite ce qui se passe
   (journal). L'ancienne grille empilait les quatre cartes sur deux rangees :
   le journal finissait sous la ligne de flottaison, invisible au moment meme
   ou il devient utile. */
.panel-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: 22px;
  align-items: start;
}

/* ------------------------------------------------------- file d'attente
 *
 * Le parc de comptes est fini : quand tout est en service, le client attend
 * son tour plutot que de partager un compte deja occupe. Le bandeau reste
 * sobre, c'est une information d'attente, pas une erreur. */
.queue-card {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 22px;
  padding: 18px 22px;
}

/* `display: flex` ci-dessus l'emporte sur le `display: none` que le navigateur
   applique aux elements [hidden] : sans cette regle, la carte restait affichee
   en permanence, sous-titre vide, alors qu'aucun compte n'etait occupe. Le
   panneau annoncait une file d'attente a des clients qui n'attendaient rien. */
.queue-card[hidden] { display: none; }

.queue-sub {
  margin: 4px 0 0;
  color: var(--muted, #8b93a7);
  font-size: 14px;
}

.queue-spin {
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.15);
  border-top-color: var(--accent, #5b8cff);
  animation: queue-spin 0.9s linear infinite;
}

@keyframes queue-spin {
  to { transform: rotate(360deg); }
}

/* Le mouvement perpetuel gene une partie des utilisateurs, et le systeme sait
   le dire : on respecte la preference plutot que d'imposer la rotation. */
@media (prefers-reduced-motion: reduce) {
  .queue-spin { animation: none; }
}

/* La colonne de droite tient toute la hauteur des trois cartes de gauche, ce
   qui donne au journal la place de defiler sans etirer la page.
   align-self evite que la carte ne s'etire au-dela de son contenu quand la
   colonne de gauche est plus haute. */
#log-panel {
  grid-column: 2;
  grid-row: 1 / span 3;
  align-self: start;
  position: sticky;
  top: 96px;
  display: flex;
  flex-direction: column;
  /* Borne haute plutot qu'etirement : sur un grand ecran la carte suivrait
     la colonne de gauche et le journal deviendrait une longue bande vide. */
  max-height: min(calc(100vh - 130px), 660px);
}

/* Les cartes de gauche gardent leur hauteur naturelle : sans cela, la
   troisieme s'etirerait pour combler la colonne et ses compteurs
   flotteraient au milieu d'un grand vide. */
.panel-grid > .panel:not(#log-panel) { align-self: start; }

/* ------------------------------------------------- respiration du panneau
 *
 * Les reglages globaux servent des formulaires courts (connexion, ticket).
 * Ici les champs s'enchainent dans une colonne etroite : au tarif commun ils
 * se touchent, et les zones cliquables — selecteur de serveur, champ d'ID,
 * message — se lisent comme un bloc compact. Tout ce qui suit ne desserre
 * que le panneau de diffusion. */

#view-panel .panel { padding: 26px 26px 28px; }
#view-panel .panel-head { margin-bottom: 22px; }

/* L'ecart entre deux champs doit rester plus grand que celui entre un champ
   et son intitule, sinon l'oeil ne sait plus a quoi se rattache l'etiquette. */
#view-panel .field { margin-bottom: 26px; }
#view-panel .field label { margin-bottom: 10px; }
#view-panel .hint { margin-top: 9px; line-height: 1.55; }

/* Zones de saisie et de selection : plus hautes, donc plus faciles a viser,
   et le texte cesse d'etre colle aux bords. */
/* :not([type="range"]) : un curseur n'a pas de texte a espacer. Le
   rembourrage l'epaissit au lieu de l'aerer, ce qui lui donnait l'aspect
   d'une grosse barre pleine. */
#view-panel input:not([type="range"]),
#view-panel select,
#view-panel textarea {
  padding: 13px 16px;
  line-height: 1.5;
}

#view-panel textarea { padding: 15px 16px; }

/* Le selecteur et son bouton « Scraper » partagent une ligne : l'ecart les
   separe assez pour qu'on ne clique pas l'un pour l'autre. */
#view-panel .row { gap: 12px; margin-bottom: 12px; }

/* Curseur de delai : piste transparente.
   Le degrade plein se lisait comme une jauge remplie a fond, alors que la
   valeur reelle est donnee par la position de la pastille. Un rail sombre
   laisse la pastille porter l'information. La regle est limitee au panneau
   pour ne pas toucher au curseur de recadrage d'avatar. */
#view-panel .slider-row { gap: 18px; }

#view-panel input[type="range"] {
  flex: 1;
  min-width: 0;
  height: 6px;
  background: rgba(124, 58, 237, 0.14);
  border: 1px solid var(--border);
  box-shadow: none;
}

#view-panel input[type="range"]:focus {
  border-color: var(--v-300);
  box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.22);
}

/* La pastille garde la couleur du theme : c'est elle qui doit accrocher
   l'oeil, une fois la piste effacee. Les deux familles de selecteurs ne
   peuvent pas etre groupees — un selecteur inconnu invaliderait la regle
   pour l'autre moteur. */
#view-panel input[type="range"]::-webkit-slider-thumb {
  width: 17px;
  height: 17px;
  background: linear-gradient(135deg, var(--v-300), var(--accent));
  border: 2px solid rgba(255, 255, 255, 0.9);
  box-shadow: 0 2px 10px rgba(124, 58, 237, 0.55);
}

#view-panel input[type="range"]::-moz-range-thumb {
  width: 17px;
  height: 17px;
  background: linear-gradient(135deg, var(--v-300), var(--accent));
  border: 2px solid rgba(255, 255, 255, 0.9);
  box-shadow: 0 2px 10px rgba(124, 58, 237, 0.55);
}

/* Les compteurs gagnent en hauteur pour s'aligner sur le reste. */
#view-panel .stat { padding: 18px 8px; }
#view-panel .stats { gap: 12px; }

.span-2 { grid-column: span 2; }

@media (max-width: 1080px) {
  .panel-grid { grid-template-columns: 1fr; }
  .span-2 { grid-column: auto; }

  /* En une colonne la position collante n'a plus de sens : le journal
     reprend sa place dans le flux, sous les reglages. */
  #log-panel {
    grid-column: auto;
    grid-row: auto;
    position: static;
    max-height: none;
  }
}

/* ---------------------------------------------------- barre de pilotage */

/* Collee sous l'en-tete (56 px) : les commandes restent atteignables quel que
   soit le defilement, y compris « Arreter » en plein envoi. */
.run-bar {
  position: sticky;
  top: 72px;
  z-index: 15;
  display: flex;
  align-items: center;
  gap: 26px;
  flex-wrap: wrap;
  margin-bottom: 22px;
  padding: 16px 22px;
  border-radius: var(--radius);
  background: rgba(18, 8, 37, 0.88);
  border: 1px solid var(--border);
  backdrop-filter: blur(18px);
  box-shadow: 0 10px 34px rgba(0, 0, 0, 0.4);
}

.run-bar-state { display: flex; align-items: center; gap: 12px; min-width: 190px; }

/* Pastille d'etat : la couleur dit l'essentiel avant meme la lecture. */
.run-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--v-500);
  transition: background 0.25s, box-shadow 0.25s;
}

.run-bar.live .run-dot {
  background: var(--ok);
  box-shadow: 0 0 0 4px rgba(74, 222, 128, 0.16);
  animation: run-pulse 1.6s ease-in-out infinite;
}

.run-bar.hold .run-dot {
  background: var(--warn);
  box-shadow: 0 0 0 4px rgba(251, 191, 36, 0.16);
}

@keyframes run-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.45; }
}

@media (prefers-reduced-motion: reduce) {
  .run-bar.live .run-dot { animation: none; }
}

/* Compte emetteur : identite Discord sous laquelle les DM partent. Separe de
   l'etat par un filet vertical, il se lit comme une information de contexte et
   non comme une commande. */
.run-account {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  padding-left: 18px;
  border-left: 1px solid var(--border);
}

/* `display: flex` ci-dessus l'emporterait sur le `display: none` applique aux
   elements [hidden] : sans cette regle, le bloc resterait visible avant toute
   attribution de compte. Meme piege que la carte de file d'attente. */
.run-account[hidden] { display: none; }

.run-account-avatar {
  flex: none;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  overflow: hidden;
  font-size: 12px;
  font-weight: 800;
  color: var(--v-100);
  background: linear-gradient(135deg, var(--v-500), var(--v-400));
  border: 1px solid var(--border);
}

/* L'image remplit le cercle sans se deformer : les avatars Discord ne sont pas
   toujours carres. */
.run-account-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }

.run-account-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }

.run-account-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

.run-account-text strong {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--v-100);
  /* Un pseudo Discord peut etre long : il se coupe plutot que de pousser la
     barre de progression hors de l'ecran. */
  max-width: 150px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Compte hors service : le nom vire au rouge et l'avatar perd ses couleurs.
   Lancer un envoi depuis ce compte echouerait, autant le voir avant. */
.run-account.is-down .run-account-text strong { color: var(--err); }
.run-account.is-down .run-account-avatar { filter: grayscale(1); opacity: 0.6; }

.run-bar-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

.run-bar-text strong {
  font-size: 13px;
  font-weight: 800;
  letter-spacing: 0.02em;
  color: var(--v-100);
}

.run-bar-sub {
  font-size: 11px;
  color: var(--muted);
  line-height: 1.4;
}

.run-bar-progress {
  flex: 1;
  min-width: 160px;
  display: flex;
  align-items: center;
  gap: 12px;
}

.run-bar-progress .progress-track { flex: 1; margin: 0; }

.run-bar-pct {
  font-size: 13px;
  font-weight: 800;
  color: var(--v-100);
  font-variant-numeric: tabular-nums;
  min-width: 42px;
  text-align: right;
}

.run-bar-actions { display: flex; gap: 9px; flex-wrap: wrap; }

@media (max-width: 720px) {
  .run-bar { gap: 12px; padding: 12px 14px; }
  .run-bar-state { min-width: 0; width: 100%; }
  .run-bar-progress { order: 3; width: 100%; }
  .run-bar-actions { order: 2; width: 100%; }
  .run-bar-actions .btn { flex: 1; }

  /* Le compte passe sur sa propre ligne, juste sous l'etat : le filet vertical
     et son retrait, prevus pour une separation horizontale, n'ont plus lieu
     d'etre. Le pseudo peut alors prendre toute la largeur disponible. */
  .run-account {
    order: 1;
    width: 100%;
    padding-left: 0;
    border-left: 0;
  }
  .run-account-text strong { max-width: none; }
}

/* ------------------------------------------------- apercu du message */

/* Rendu du message tel qu'il apparaitra dans Discord. La zone de saisie au
   dessus montre du texte brut : gras, liens et sauts de ligne ne se voient
   qu'ici, avant l'envoi plutot qu'apres. */
.dm-preview { margin-top: 16px; }

/* `display: flex` plus bas sur .dm-bubble n'affecte pas ce conteneur, mais on
   fixe la regle par principe : tout bloc masquable du panneau doit respecter
   [hidden] (voir la carte de file d'attente, qui restait affichee). */
.dm-preview[hidden] { display: none; }

.dm-preview-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}

.dm-preview-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ------------------------------------------- boite de composition (Discord) */

/* Discord n'affiche pas un champ nu : le texte et ses outils vivent dans une
   meme boite arrondie, qui s'allume au focus. On reproduit cela, en violet.
   Le cadre est porte par ce conteneur, pas par le textarea : c'est ce qui
   permet a la barre d'outils de paraitre dans la meme surface. */
/* Bouton « + » d'ajout d'image, a gauche du champ comme dans Discord. Rond,
   discret au repos, il s'eclaire au survol. `align-self: flex-end` le garde
   colle en bas quand le champ grandit sur plusieurs lignes -- il resterait
   sinon suspendu au milieu de la boite. */
.dm-joindre {
  flex: none;
  align-self: flex-end;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  /* (44 - 30) / 2 = 7 : centre le bouton sur la hauteur d'une ligne. */
  margin: 0 2px 7px 9px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--muted);
  color: rgba(10, 4, 19, 0.92);
  cursor: pointer;
  transition: background 0.16s, transform 0.16s;
}

.dm-joindre svg {
  width: 17px;
  height: 17px;
  stroke: currentColor;
  fill: none;
}

.dm-joindre:hover { background: var(--v-200); transform: scale(1.06); }
.dm-joindre:active { transform: scale(0.94); }

.dm-joindre:focus-visible {
  outline: 2px solid var(--v-300);
  outline-offset: 2px;
}

/* Une image deja jointe : le bouton bascule en violet plein, pour qu'on voie
   d'un coup d'oeil qu'il y a une piece attachee meme vignette repliee. */
.dm-joindre.actif { background: var(--v-400); color: #fff; }

.dm-compose {
  display: flex;
  align-items: flex-end;
  gap: 10px;
  padding-right: 14px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: rgba(10, 4, 19, 0.6);
  transition: border-color 0.18s, box-shadow 0.18s;
}

/* Le focus se peint sur la boite entiere. `focus-within` plutot que `focus` :
   cliquer un bouton de mise en forme ne doit pas eteindre le cadre. */
.dm-compose:focus-within {
  border-color: var(--v-300);
  box-shadow: 0 0 0 3px rgba(168, 85, 247, 0.16);
}

/* Le champ perd son propre cadre : il devient la surface de la boite. La
   specificite (#view-panel plus haut) impose de reprendre le rembourrage.
   
   Hauteur : une seule ligne au depart, comme la barre de Discord. La boite
   grandit ensuite a chaque retour a la ligne (hauteur pilotee en JS par
   ajusterSaisie), jusqu'a un plafond au-dela duquel on defile. `resize: none`
   parce que la poignee manuelle n'a plus de sens quand la hauteur s'ajuste
   toute seule -- et elle depassait du coin arrondi. */
.dm-compose textarea,
#view-panel .dm-compose textarea {
  display: block;
  flex: 1;
  min-width: 0;
  border: none;
  background: none;
  border-radius: 0;
  box-shadow: none;
  /* Rembourrage symetrique (12 px en haut comme en bas) : dissymetrique, le
     texte n'etait pas centre sur la ligne et paraissait rogne en bas.
     A gauche, rien : c'est le bouton « + » qui tient cette marge. */
  padding: 12px 0;
  /* Police emoji en repli, apres celle du site : un emoji insere doit
     s'afficher en couleur ici comme dans Discord, pas en noir et blanc.
     Le nom reel est repete car `inherit` n'est pas admis dans une liste de
     polices -- l'y glisser invaliderait toute la declaration. */
  font-family: "Outfit", system-ui, "Apple Color Emoji", "Segoe UI Emoji",
    "Noto Color Emoji", sans-serif;
  font-size: 14.5px;
  line-height: 20px;
  resize: none;
  min-height: 0;
  /* 44 px = 20 (ligne) + 24 (rembourrage). Toute autre valeur decale le texte
     verticalement des l'etat vide. */
  height: 44px;
  max-height: 260px;
  /* Masque par defaut, et non `auto` : sur une boite d'une seule ligne, un
     depassement d'un pixel suffisait a faire surgir le curseur de defilement,
     qui remplissait toute la piste -- une grosse pastille violette collee au
     bord droit. Le defilement n'est reactive (classe .defile, posee par
     ajusterSaisie) que lorsque le texte depasse reellement le plafond. */
  overflow-y: hidden;
}

.dm-compose textarea.defile,
#view-panel .dm-compose textarea.defile { overflow-y: auto; }

/* Barre fine et sobre : la barre globale du site est un degrade epais, trop
   voyant dans une zone de saisie de cette taille. */
.dm-compose textarea {
  scrollbar-width: thin;
  scrollbar-color: rgba(124, 58, 237, 0.45) transparent;
}
.dm-compose textarea::-webkit-scrollbar { width: 6px; }
.dm-compose textarea::-webkit-scrollbar-track { background: transparent; }
.dm-compose textarea::-webkit-scrollbar-thumb {
  background: rgba(124, 58, 237, 0.45);
  border-radius: 999px;
}
.dm-compose textarea::-webkit-scrollbar-thumb:hover { background: rgba(168, 85, 247, 0.65); }

.dm-compose textarea:focus,
#view-panel .dm-compose textarea:focus {
  border: none;
  box-shadow: none;
}

/* Compteur discret, aligne sur la derniere ligne du champ. Il prend la place
   qu'occupent les icones dans la barre de Discord. */
.dm-compose-compteur {
  flex: none;
  padding-bottom: 13px;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  opacity: 0.7;
  white-space: nowrap;
  /* Le compteur ne doit pas voler le clic destine au champ : cliquer a droite
     de son texte doit poser le curseur dans la zone de saisie. */
  pointer-events: none;
  user-select: none;
}

/* -------------------------------------------------------- emojis du message */

/* Bouton emoji, a droite du compteur comme dans Discord. Meme gabarit que le
   « + » a gauche, pour que la barre reste equilibree. */
.dm-emoji {
  flex: none;
  align-self: flex-end;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  /* Les 30 px comprennent la bordure : sans cela une regle `button` du site
     ajoutait 2 px et le bouton depassait celui de l'image, d'ou une barre
     visiblement desequilibree. */
  box-sizing: border-box;
  /* 7 px de bas : aligne le bouton sur la ligne de texte, comme le « + ». */
  margin: 0 0 7px 2px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: none;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.16s, background 0.16s, transform 0.16s;
}

.dm-emoji svg {
  width: 21px;
  height: 21px;
  stroke: currentColor;
  fill: none;
}

/* Les yeux sont pleins : en trait seul ils disparaissent a cette taille. */
.dm-emoji .dm-emoji-oeil {
  fill: currentColor;
  stroke: none;
}

.dm-emoji:hover {
  color: var(--v-200);
  background: rgba(124, 58, 237, 0.16);
  transform: scale(1.06);
}

.dm-emoji:active { transform: scale(0.94); }

.dm-emoji:focus-visible {
  outline: 2px solid var(--v-300);
  outline-offset: 2px;
}

/* Panneau ouvert : le bouton reste allume, on voit d'ou vient la fenetre. */
.dm-emoji.ouvert {
  color: var(--v-200);
  background: rgba(124, 58, 237, 0.2);
}

.dm-compose-bloc { position: relative; }

/* Le panneau s'inscrit dans le flux plutot que de flotter au-dessus.
   
   En absolu il etait inutilisable : la carte porte `overflow: hidden`, qui le
   coupait net a son bord, et `backdrop-filter`, qui cree un contexte
   d'empilement dont aucun `z-index` ne sort -- l'en-tete de la carte suivante
   passait devant et avalait les clics sur les premiers emojis.
   
   Dans le flux, il pousse simplement ce qui suit : rien ne peut le recouvrir,
   et il reste entierement cliquable. */
.dm-emoji-panneau {
  margin: 8px 0 0 auto;
  width: 320px;
  padding: 10px;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: #17081f;
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.5);
}

/* `[hidden]` doit l'emporter sur le `position`/`display` ci-dessus : sans
   cela le panneau reste affiche en permanence. */
.dm-emoji-panneau[hidden] { display: none; }

.dm-emoji-recherche { margin-bottom: 8px; }

.dm-emoji-recherche input,
#view-panel .dm-emoji-recherche input {
  width: 100%;
  padding: 7px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: rgba(10, 4, 19, 0.6);
  color: var(--text);
  font-size: 13px;
}

.dm-emoji-recherche input:focus,
#view-panel .dm-emoji-recherche input:focus {
  border-color: var(--v-300);
  box-shadow: 0 0 0 3px rgba(168, 85, 247, 0.16);
  outline: none;
}

/* Grille fixe a 8 colonnes : le nombre d'emojis par ligne ne doit pas changer
   pendant la recherche, l'oeil perdrait ses reperes. */
.dm-emoji-liste {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  gap: 2px;
  max-height: 232px;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: rgba(124, 58, 237, 0.45) transparent;
}

.dm-emoji-liste::-webkit-scrollbar { width: 6px; }
.dm-emoji-liste::-webkit-scrollbar-track { background: transparent; }
.dm-emoji-liste::-webkit-scrollbar-thumb {
  background: rgba(124, 58, 237, 0.45);
  border-radius: 999px;
}

/* Titre de famille : il traverse toute la grille. */
.dm-emoji-famille {
  grid-column: 1 / -1;
  padding: 8px 4px 3px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.dm-emoji-choix {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
  padding: 0;
  border: none;
  border-radius: 7px;
  background: none;
  /* Police emoji explicite. La police du site (Outfit) n'en contient pas :
     sans ce repli, le navigateur retombe sur un glyphe monochrome et les
     emojis s'affichent en noir et blanc. */
  font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji",
    "Twemoji Mozilla", sans-serif;
  /* `line-height: 1` evite qu'ils soient rognes en haut dans une case aussi
     serree. */
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  transition: background 0.12s, transform 0.12s;
}

.dm-emoji-choix:hover {
  background: rgba(124, 58, 237, 0.24);
  transform: scale(1.14);
}

.dm-emoji-choix:focus-visible {
  outline: 2px solid var(--v-300);
  outline-offset: -2px;
}

.dm-emoji-vide {
  margin: 12px 0 4px;
  text-align: center;
  font-size: 12.5px;
  color: var(--muted);
}

.dm-emoji-vide[hidden] { display: none; }

/* ------------------------------------------------- image jointe au message */

/* Vignette de la piece jointe, sous la barre de saisie. Discord la place
   au-dessus du champ ; ici la carte « message » est deja surmontee de son
   titre, la poser dessous garde le champ a sa hauteur habituelle. */
.dm-jointe { margin-top: 10px; }

/* `[hidden]` doit l'emporter sur le `display` de la regle ci-dessus : sans
   cela, la vignette reste visible alors qu'aucune image n'est choisie -- le
   piege deja rencontre sur la carte de file d'attente. */
.dm-jointe[hidden] { display: none; }

.dm-jointe-carte {
  display: flex;
  align-items: center;
  gap: 12px;
  max-width: 340px;
  padding: 8px 10px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: rgba(10, 4, 19, 0.6);
}

.dm-jointe-vignette {
  flex: none;
  width: 46px;
  height: 46px;
  border-radius: 7px;
  /* `cover` : une image tres large ou tres haute doit remplir le carre sans se
     deformer, comme le fait l'apercu de Discord. */
  object-fit: cover;
  background: rgba(124, 58, 237, 0.16);
}

/* `min-width: 0` : sans lui, un nom de fichier long refuse de se tronquer et
   pousse le bouton de retrait hors de la carte. */
.dm-jointe-infos {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}

.dm-jointe-nom {
  font-size: 12.5px;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dm-jointe-poids {
  font-size: 11px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.dm-jointe-retirer {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: none;
  border-radius: 7px;
  background: none;
  color: var(--muted);
  cursor: pointer;
  transition: background 0.16s, color 0.16s;
}

.dm-jointe-retirer svg {
  width: 15px;
  height: 15px;
  stroke: currentColor;
  fill: none;
}

.dm-jointe-retirer:hover {
  background: rgba(237, 66, 69, 0.16);
  color: #f87171;
}

.dm-jointe-retirer:focus-visible {
  outline: 2px solid var(--v-300);
  outline-offset: 1px;
}

/* Image dans l'apercu du rendu : meme cadre arrondi que dans Discord, taille
   plafonnee pour qu'une grande image ne fasse pas exploser la bulle. */
.dm-bubble-image {
  display: block;
  max-width: 300px;
  max-height: 220px;
  width: auto;
  height: auto;
  margin-top: 8px;
  border-radius: 8px;
  object-fit: contain;
}

/* Fenetre de messages prives : barre de salon en haut, message dessous. Le
   fond sombre et la disposition sont ceux de Discord, mais les teintes tirent
   vers le violet du site plutot que vers le gris bleute d'origine. */
.dm-fenetre {
  border-radius: 14px;
  overflow: hidden;
  border: 1px solid var(--border);
  background: linear-gradient(180deg, #170a2b 0%, #120722 100%);
  box-shadow: 0 10px 30px rgba(10, 4, 19, 0.45), inset 0 1px 0 rgba(233, 213, 255, 0.05);
}

/* Barre de salon : reprend l'en-tete de Discord (icone, nom, etiquette). */
.dm-fenetre-barre {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 14px;
  border-bottom: 1px solid var(--border);
  background: rgba(42, 21, 83, 0.45);
}

.dm-fenetre-icone {
  flex: none;
  width: 15px;
  height: 15px;
  fill: var(--muted);
}

.dm-fenetre-titre {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--v-100);
  /* Un nom de compte long ne doit pas pousser l'etiquette hors de la barre. */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.dm-fenetre-tag {
  flex: none;
  margin-left: auto;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--v-200);
  padding: 2px 7px;
  border-radius: 999px;
  background: rgba(124, 58, 237, 0.2);
  border: 1px solid var(--border);
}

/* La ligne de message imite Discord : avatar a gauche, auteur et horodatage en
   tete, texte dessous. Le survol eclaire la ligne, comme dans l'application. */
.dm-bubble {
  display: flex;
  gap: 13px;
  padding: 13px 15px 15px;
  transition: background 0.15s ease;
}

.dm-bubble:hover { background: rgba(124, 58, 237, 0.07); }

.dm-bubble-avatar {
  flex: none;
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  overflow: hidden;
  font-size: 14px;
  font-weight: 800;
  color: var(--v-100);
  background: linear-gradient(135deg, var(--v-500), var(--v-400));
  border: 1px solid var(--border);
}

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

.dm-bubble-body { min-width: 0; flex: 1; }

.dm-bubble-top {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 2px;
}

/* Le pseudo est colore, comme un membre ayant un role sur Discord. */
.dm-bubble-author {
  font-size: 14px;
  font-weight: 600;
  color: var(--v-200);
  /* Un pseudo long ne doit pas repousser l'horodatage hors de la bulle. */
  max-width: 190px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.dm-bubble-time {
  font-size: 10.5px;
  color: var(--muted);
  opacity: 0.75;
}

.dm-bubble-text {
  font-size: 14px;
  /* Meme repli emoji que la zone de saisie : l'apercu doit montrer le message
     tel qu'il arrivera dans Discord, emojis en couleur compris. */
  font-family: "Outfit", system-ui, "Apple Color Emoji", "Segoe UI Emoji",
    "Noto Color Emoji", sans-serif;
  /* 1.375 est l'interlignage de Discord : c'est ce qui donne l'aspect aere
     des messages, plus que les couleurs. */
  line-height: 1.375;
  color: var(--text);
  /* Le message est une saisie libre : un mot tres long ou une URL sans espace
     doit se couper plutot que d'elargir la carte. */
  /* Pas de `white-space: pre-wrap` ici : peindreMessage() insere deja des <br>
     pour les sauts de ligne, et les deux cumules doublaient chaque retour. */
  overflow-wrap: anywhere;
}

/* Mise en forme Discord. Les classes sont posees par fragmenter(), qui gere
   aussi l'imbrication : un fragment peut porter « dm-gras dm-ital ». */
.dm-gras, .dm-gras-ital { font-weight: 700; }
.dm-ital, .dm-gras-ital { font-style: italic; }
.dm-souligne { text-decoration: underline; }
.dm-barre { text-decoration: line-through; }

.dm-code, .dm-code-bloc {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12.5px;
  border-radius: 4px;
  background: rgba(10, 4, 19, 0.75);
  border: 1px solid var(--border);
  color: var(--v-100);
  padding: 1px 5px;
}

/* Le bloc de code occupe sa propre ligne, comme dans Discord. */
.dm-code-bloc {
  display: block;
  padding: 9px 11px;
  margin: 5px 0;
  white-space: pre-wrap;
}

/* Spoiler : pave opaque jusqu'au survol, comme dans Discord. Le texte reste
   selectionnable, on ne fait que le couvrir. */
.dm-spoiler {
  background: var(--v-600);
  border-radius: 4px;
  padding: 0 2px;
  color: transparent;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.dm-spoiler:hover { background: rgba(42, 21, 83, 0.6); color: var(--text); }

.dm-lien { color: var(--v-200); text-decoration: none; }
.dm-lien:hover { text-decoration: underline; }

/* --------------------------------------------------- etapes du panneau */

/* Prefixe dm- volontaire : .step et .step-num appartiennent deja au guide,
   ou .step est un « display: flex » en ligne. Reutiliser ces noms depliait
   les cartes du panneau a l'horizontale — chaque champ devenait une colonne.
   Les selecteurs ci-dessous ne peuvent plus entrer en collision. */
.dm-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  margin-right: 10px;
  border-radius: 50%;
  background: rgba(124, 58, 237, 0.3);
  border: 1px solid var(--border);
  color: var(--v-100);
  font-size: 11px;
  font-weight: 800;
  flex-shrink: 0;
}

.panel.dm-card .panel-head h2 { display: flex; align-items: center; }

/* Etape accomplie : le numero passe au vert, on voit d'un coup d'oeil ce
   qu'il reste a faire. */
.panel.dm-card.done .dm-num {
  background: rgba(74, 222, 128, 0.2);
  border-color: rgba(74, 222, 128, 0.45);
  color: #86efac;
}

.panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 22px;
  backdrop-filter: blur(18px);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45);
  position: relative;
  overflow: hidden;
}

.panel::before {
  content: "";
  position: absolute;
  top: 0; left: 12%;
  width: 76%; height: 1px;
  background: linear-gradient(90deg, transparent, var(--v-300), transparent);
  opacity: 0.7;
}

.panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 18px;
}

.panel-head h2 {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--v-200);
}

.badge {
  font-size: 12px;
  font-weight: 700;
  padding: 4px 11px;
  border-radius: 999px;
  color: #fff;
  background: linear-gradient(135deg, var(--v-400), var(--accent));
}

.badge.soft {
  background: rgba(124, 58, 237, 0.2);
  color: var(--v-200);
  border: 1px solid var(--border);
}

.badge.live { background: linear-gradient(135deg, var(--ok), #16a34a); }
.badge.hold { background: linear-gradient(135deg, var(--warn), #d97706); }

/* --------------------------------------------------------------- champs */

.field { margin-bottom: 18px; }
.field:last-child { margin-bottom: 0; }

.field label {
  display: block;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--v-200);
  margin-bottom: 8px;
}

.row { display: flex; gap: 9px; margin-bottom: 9px; }
.row > select { flex: 1; min-width: 0; }

input, select, textarea {
  width: 100%;
  padding: 12px 14px;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: rgba(10, 4, 19, 0.6);
  color: var(--text);
  font-family: inherit;
  font-size: 14px;
  outline: none;
  transition: border-color 0.18s, box-shadow 0.18s;
}

textarea { resize: vertical; line-height: 1.6; }

input:focus, select:focus, textarea:focus {
  border-color: var(--v-300);
  box-shadow: 0 0 0 3px rgba(168, 85, 247, 0.16);
}

input::placeholder, textarea::placeholder { color: rgba(167, 139, 250, 0.5); }

select option { background: var(--v-800); color: var(--text); }

.mono { font-family: "JetBrains Mono", monospace; font-size: 13px; }

.hint { font-size: 11px; color: var(--muted); margin-top: 6px; }

.slider-row { display: flex; align-items: center; gap: 14px; }

input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  padding: 0;
  height: 6px;
  border: none;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--v-400), var(--accent));
  box-shadow: 0 0 16px rgba(124, 58, 237, 0.4);
}

input[type="range"]:focus { box-shadow: 0 0 16px rgba(124, 58, 237, 0.4); }

input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: #fff;
  cursor: pointer;
  box-shadow: 0 0 12px var(--v-200);
  transition: transform 0.15s;
}

input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.2); }

input[type="range"]::-moz-range-thumb {
  width: 18px; height: 18px;
  border: none;
  border-radius: 50%;
  background: #fff;
  cursor: pointer;
  box-shadow: 0 0 12px var(--v-200);
}

.slider-value {
  min-width: 62px;
  text-align: right;
  font-family: "JetBrains Mono", monospace;
  font-size: 13px;
  font-weight: 700;
  color: var(--v-200);
}

/* -------------------------------------------------------------- boutons */

.btn {
  padding: 12px 20px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: rgba(124, 58, 237, 0.16);
  color: var(--text);
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  transition: transform 0.16s, background 0.16s, box-shadow 0.16s, opacity 0.16s;
  white-space: nowrap;
}

.btn:hover:not(:disabled) {
  transform: translateY(-2px);
  background: rgba(124, 58, 237, 0.3);
  box-shadow: 0 8px 24px rgba(124, 58, 237, 0.32);
}

.btn:active:not(:disabled) { transform: translateY(0); }

.btn:disabled { opacity: 0.38; cursor: not-allowed; }

/* Reflet qui balaye le bouton au survol. */
.btn::after {
  content: "";
  position: absolute;
  top: 0; left: -120%;
  width: 60%; height: 100%;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,0.22), transparent);
  transform: skewX(-20deg);
  transition: left 0.55s;
}

.btn:hover:not(:disabled)::after { left: 140%; }

.btn.primary {
  background: linear-gradient(135deg, var(--v-400), var(--accent));
  border-color: transparent;
  font-weight: 700;
  box-shadow: 0 6px 22px rgba(124, 58, 237, 0.45);
}

.btn.primary:hover:not(:disabled) { box-shadow: 0 10px 34px rgba(217, 70, 239, 0.55); }

.btn.danger { background: rgba(251, 113, 133, 0.16); border-color: rgba(251, 113, 133, 0.35); }
.btn.danger:hover:not(:disabled) { background: rgba(251, 113, 133, 0.28); box-shadow: 0 8px 24px rgba(251,113,133,0.3); }

.btn.small { padding: 8px 15px; font-size: 13px; }

/* Action secondaire : « Vider » ne doit pas attirer l'oeil autant que les
   commandes d'envoi voisines. */
.btn.ghost {
  background: transparent;
  border-color: var(--border);
  color: var(--muted);
}

.btn.ghost:hover {
  background: rgba(124, 58, 237, 0.14);
  color: var(--v-100);
}
.btn.lg { padding: 15px 30px; font-size: 15.5px; }

.actions { display: flex; gap: 10px; }
.actions .btn { flex: 1; }

/* ------------------------------------------------------------ progression */

.progress-track {
  height: 9px;
  border-radius: 999px;
  background: rgba(10, 4, 19, 0.7);
  overflow: hidden;
  border: 1px solid var(--border);
}

.progress-fill {
  height: 100%;
  width: 0%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--v-400), var(--accent), var(--v-200));
  background-size: 200% 100%;
  animation: flow 2.4s linear infinite;
  transition: width 0.45s cubic-bezier(0.2, 0.8, 0.2, 1);
  box-shadow: 0 0 18px rgba(217, 70, 239, 0.6);
}

@keyframes flow { to { background-position: 200% 0; } }

/* Trois compteurs : le pourcentage vit desormais dans la barre de pilotage,
   au plus pres de la barre de progression qu'il chiffre. */
.stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  margin-top: 4px;
}

@media (max-width: 620px) {
  .stats { grid-template-columns: repeat(2, 1fr); }
}

.stat {
  text-align: center;
  padding: 14px 6px;
  border-radius: 13px;
  background: rgba(124, 58, 237, 0.1);
  border: 1px solid var(--border);
}

.stat-value {
  display: block;
  font-size: 24px;
  font-weight: 900;
  color: var(--v-100);
  font-variant-numeric: tabular-nums;
}

.stat-value.warn { color: var(--warn); }

.stat-label {
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ------------------------------------------- cadence, duree et journal */

/* Ligne discrete sous les compteurs : ce sont des estimations, elles ne
   doivent pas peser autant que les chiffres reels au-dessus. */
.run-meta {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: 14px;
  padding: 10px 14px;
  border-radius: 12px;
  background: rgba(124, 58, 237, 0.07);
  border: 1px solid var(--border);
  flex-wrap: wrap;
}

.run-meta-item { display: flex; align-items: baseline; gap: 8px; }

.run-meta-label {
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

.run-meta-value {
  font-size: 14px;
  font-weight: 800;
  color: var(--v-100);
  font-variant-numeric: tabular-nums;
}

.run-meta-sep {
  width: 1px;
  height: 16px;
  background: var(--border);
}

@media (max-width: 620px) {
  .run-meta-sep { display: none; }
}

.log-tools { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

.log-filters {
  display: flex;
  gap: 2px;
  padding: 3px;
  border-radius: 10px;
  background: rgba(10, 4, 19, 0.5);
  border: 1px solid var(--border);
}

/* Trois cibles cliquables cote a cote : sans hauteur suffisante on vise
   « echecs » et on obtient « envoyes ». */
.log-filter {
  border: 0;
  cursor: pointer;
  padding: 8px 15px;
  border-radius: 8px;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  transition: background 0.15s, color 0.15s;
}

.log-filter:hover { color: var(--v-100); }

.log-filter.active {
  background: rgba(124, 58, 237, 0.35);
  color: #fff;
}

/* Le journal occupe la hauteur restante de sa colonne : il grandit avec
   l'ecran au lieu d'etre fige a une taille arbitraire, tout en gardant son
   propre defilement pour ne jamais etirer la page. */
.log-box {
  flex: 1;
  min-height: 320px;
  overflow-y: auto;
  padding: 14px 16px;
  border-radius: 13px;
  background: rgba(10, 4, 19, 0.55);
  border: 1px solid var(--border);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  line-height: 1.7;
  scrollbar-width: thin;
  scrollbar-color: var(--v-500) transparent;
}

.log-box::-webkit-scrollbar { width: 8px; }
.log-box::-webkit-scrollbar-track { background: transparent; }
.log-box::-webkit-scrollbar-thumb {
  background: var(--v-500);
  border-radius: 8px;
}

.log-empty {
  color: var(--muted);
  font-family: inherit;
  font-size: 12px;
  padding: 14px 4px;
  text-align: center;
}

/* Les lignes se suivent de pres par nature : un peu de hauteur et d'ecart
   evitent qu'elles ne forment un pave illisible pendant un envoi rapide. */
.log-line {
  display: flex;
  gap: 14px;
  padding: 4px 7px;
  border-radius: 6px;
  word-break: break-word;
}

.log-line + .log-line { margin-top: 3px; }

.log-time {
  color: var(--v-500);
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
}

.log-text { color: var(--text); flex: 1; min-width: 0; }

/* La couleur porte le sens : on doit pouvoir balayer le journal et reperer un
   echec sans lire chaque ligne. */
.log-line.ok    .log-text { color: #86efac; }
.log-line.err   .log-text { color: var(--err); }
.log-line.warn  .log-text { color: var(--warn); }
.log-line.info  .log-text { color: var(--v-100); }

.log-line.err { background: rgba(251, 113, 133, 0.07); }

.log-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  margin-top: 14px;
  flex-wrap: wrap;
}

.log-follow {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: var(--muted);
  cursor: pointer;
  user-select: none;
}

.log-follow input { accent-color: var(--v-400); cursor: pointer; }

.log-count {
  font-size: 11px;
  color: var(--v-500);
  font-variant-numeric: tabular-nums;
}

/* Le journal n'a d'interet qu'en pleine largeur : sur mobile la grille passe
   deja en une colonne, on ajuste seulement la hauteur. */
@media (max-width: 620px) {
  .log-box { min-height: 200px; font-size: 11px; }
}

/* --------------------------------------------------------------- footer */

.footer {
  position: relative;
  z-index: 2;
  display: flex;
  justify-content: center;
  gap: 10px;
  padding: 22px 34px 28px;
  font-size: 12.5px;
  color: var(--muted);
  border-top: 1px solid var(--border);
}

.footer .sep { opacity: 0.5; }

/* --------------------------------------------------------------- toasts */

.toast-stack {
  position: fixed;
  right: 22px;
  bottom: 22px;
  z-index: 40;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.toast {
  padding: 13px 18px;
  border-radius: 13px;
  background: var(--panel);
  border: 1px solid var(--v-300);
  backdrop-filter: blur(18px);
  box-shadow: 0 12px 36px rgba(0, 0, 0, 0.5);
  font-size: 13px;
  max-width: 340px;
  animation: toast-in 0.32s cubic-bezier(0.2, 0.9, 0.3, 1.2) both;
}

.toast.err { border-color: var(--err); color: #ffe4e6; }
.toast.ok  { border-color: var(--ok); }
.toast.out { animation: toast-out 0.28s ease forwards; }

@keyframes toast-in {
  from { opacity: 0; transform: translateX(60px) scale(0.94); }
  to   { opacity: 1; transform: none; }
}

@keyframes toast-out {
  to { opacity: 0; transform: translateX(60px) scale(0.94); }
}

/* Barres de defilement violettes, sinon le gris systeme casse le theme. */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: linear-gradient(var(--v-400), var(--accent));
  border-radius: 999px;
}
::-webkit-scrollbar-thumb:hover { background: var(--v-300); }

/* ------------------------------------------------------------ responsive */

@media (max-width: 1100px) {
  .topbar { flex-wrap: wrap; padding: 14px 20px; }
  /* La barre defile a cette largeur. Attention : un conteneur qui defile
     n'ancre pas ses enfants absolus a son contenu mais a son cadre visible, et
     la pastille resterait figee pendant que les onglets glissent sous elle. On
     laisse donc la barre garder sa largeur naturelle (min-width: max-content)
     et c'est le bandeau qui defile a sa place, ce qui preserve l'ancrage. */
  .nav {
    order: 3;
    width: 100%;
    min-width: max-content;
    justify-content: center;
  }
  .topbar { overflow-x: auto; scrollbar-width: none; }
  /* Le defilement reste possible au doigt, mais sans rail visible : une barre
     grise sous le bandeau salissait l'en-tete. */
  .topbar::-webkit-scrollbar { display: none; }
  /* La navigation passe sur sa propre ligne : le partage en deux moities n'a
     plus lieu d'etre, la marque et la pastille se repartissent la ligne du
     haut avec le space-between du .topbar. */
  .topbar-left, .topbar-right { flex: 0 1 auto; }
  .views { padding: 32px 20px 48px; }
}

@media (max-width: 620px) {
  .nav-link { padding: 8px 13px; font-size: 12.5px; }
  .hero-actions .btn { flex: 1; }
  .actions { flex-direction: column; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
  html { scroll-behavior: auto; }
}

.user-chip {
  position: relative;
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 5px 6px 5px 13px;
  border-radius: 999px;
  background:
    linear-gradient(135deg, rgba(45, 21, 92, 0.5), rgba(10, 4, 19, 0.62));
  border: 1px solid var(--border);
  box-shadow: 0 2px 14px rgba(124, 58, 237, 0.14);
  transition: border-color 0.25s ease, box-shadow 0.25s ease;
}

/* Halo pose derriere la pastille plutot qu'en box-shadow : il peut ainsi
   depasser du contour arrondi sans elargir la boite ni bousculer la barre.
   Il ne se revele qu'au survol, pour que la barre reste calme au repos. */
.user-chip::before {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(120deg, transparent 20%, rgba(168, 85, 247, 0.55) 50%, transparent 80%);
  background-size: 220% 100%;
  /* Ne peint que le contour : le degrade est masque partout sauf sur la bordure
     dessinee par le padding de 1px. */
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask-composite: exclude;
  opacity: 0;
  transition: opacity 0.3s ease;
  pointer-events: none;
}

.user-chip:hover {
  border-color: rgba(168, 85, 247, 0.45);
  box-shadow: 0 4px 22px rgba(124, 58, 237, 0.3);
}

.user-chip:hover::before {
  opacity: 1;
  animation: chip-border 2.4s linear infinite;
}

/* display:flex ci-dessus l'emporte sur le display:none que le navigateur
   applique a [hidden] : sans cette regle, une capsule vide restait affichee
   dans la barre tant que le profil n'etait pas charge. */
.user-chip[hidden] { display: none; }

@keyframes chip-border { to { background-position: 220% 0; } }

/* Regle generique de la pastille. Attention : son selecteur (0,1,1) l'emporte
   sur .role-tag (0,1,0), d'où les selecteurs .user-chip .role-tag plus bas
   pour reprendre la main sur la taille et la couleur de l'etiquette. */
.user-chip span {
  font-size: 12.5px;
  color: var(--v-200);
  max-width: 170px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Le pseudo est devenu un bouton pour ouvrir la fiche du compte. Il doit
   garder l'apparence d'un simple libelle : on efface la parure native du
   bouton et on reprend les proprietes que .user-chip span posait quand il
   n'etait qu'un span. */
.user-chip .chip-name {
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  /* Le bouton ne porte plus de texte visible : il se cale sur la photo. Les
     regles de troncature d'origine n'ont plus d'objet. */
  display: flex;
  align-items: center;
  position: relative;
  transition: color 0.2s ease, opacity 0.2s ease;
}

/* La barre n'affiche plus que la photo : le pseudo reste dans le bouton pour
   les lecteurs d'ecran, retire de l'affichage sans display:none qui le
   sortirait aussi de l'arbre d'accessibilite. */
.chip-pseudo {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.chip-avatar {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  overflow: hidden;
  background: linear-gradient(135deg, var(--v-400), #c026d3);
  /* Le contour separe la photo du fond de la pastille : sans lui, une image
     sombre se fondait dans la barre et le disque perdait sa forme. */
  border: 1px solid rgba(168, 85, 247, 0.35);
  font-family: "Outfit", system-ui, sans-serif;
  font-size: 13px;
  font-weight: 900;
  color: #fff;
  transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
}

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

/* Le souligne d'origine n'a plus d'objet sans texte visible : la photo
   s'eclaire et avance d'un cheveu pour dire qu'elle repond. */
.user-chip .chip-name:hover .chip-avatar {
  border-color: rgba(216, 180, 254, 0.75);
  box-shadow: 0 0 0 3px rgba(168, 85, 247, 0.22);
  transform: scale(1.05);
}

.user-chip.is-owner .chip-avatar {
  border-color: rgba(216, 180, 254, 0.6);
  box-shadow: 0 0 12px rgba(168, 85, 247, 0.35);
}

.user-chip .chip-name:focus-visible {
  outline: 2px solid var(--v-300);
  outline-offset: 3px;
  border-radius: 6px;
}

/* Le bouton du compte ne porte plus de pseudo visible, seulement la photo : la
   mise en forme du texte (Outfit 900) et le degrade anime du rang owner n'ont
   donc plus rien a habiller.

   Ce degrade tenait par background-clip: text, qui ne peint que la silhouette
   des lettres. Le pseudo etant desormais reduit a un point pour les lecteurs
   d'ecran, il ne restait plus de silhouette a decouper : le fond se peignait
   alors en plein sur toute la boite du bouton et formait un rectangle violet
   derriere le cercle. D'ou la suppression plutot que l'ajustement.
   
   L'identite du rang reste portee par l'etiquette a cote et par le contour de
   la photo, tous deux inchanges. */

/* -------- bouton de deconnexion -------- */

/* Volontairement discret au repos : c'est une sortie, pas une action que l'on
   cherche a provoquer. Il ne prend sa teinte rouge qu'au survol. */
.chip-logout {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px 14px 7px 12px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  color: var(--v-200);
  background: rgba(124, 58, 237, 0.12);
  border-color: rgba(168, 85, 247, 0.22);
  transition: color 0.2s ease, background 0.2s ease,
              border-color 0.2s ease, box-shadow 0.2s ease;
}

.chip-logout-icon {
  width: 15px;
  height: 15px;
  flex: none;
  /* La fleche avance vers la sortie au survol : le mouvement dit le geste. */
  transition: transform 0.25s cubic-bezier(0.22, 1, 0.36, 1);
}

/* Rouge reserve au survol direct : l'avertissement arrive au moment ou le
   curseur est reellement sur le bouton, pas avant. */
.chip-logout:hover:not(:disabled) {
  color: #ffe4e6;
  background: rgba(251, 113, 133, 0.26);
  border-color: rgba(251, 113, 133, 0.5);
  box-shadow: 0 6px 20px rgba(251, 113, 133, 0.32);
}

.chip-logout:hover:not(:disabled) .chip-logout-icon { transform: translateX(3px); }

.chip-logout:active:not(:disabled) .chip-logout-icon { transform: translateX(1px); }

/* La regle generique .btn souleve le bouton de 2px au survol. Dans une pastille
   arrondie, ce decalage desaxe le bouton de son logement : on l'annule ici. */
.chip-logout:hover:not(:disabled),
.chip-logout:active:not(:disabled) { transform: none; }

@media (max-width: 860px) {
  /* Le pseudo cede la place faute de largeur, mais l'etiquette de role reste :
     le selecteur generique masquait aussi le badge owner, alors que c'est
     justement l'element a mettre en avant.
     .chip-name est nomme explicitement : le pseudo est devenu un bouton, il
     n'est plus capte par le selecteur des span. */
  .user-chip span,
  .user-chip .chip-name { display: none; }
  /* L'etiquette prend le relais du pseudo pour ouvrir la fiche du compte : le
     curseur doit le dire, sinon rien ne signale qu'elle est cliquable. */
  .user-chip .role-tag { display: inline-block; cursor: pointer; }

  /* Le libelle tombe avec les autres span, mais le bouton doit rester : on le
     reduit a son icone, dans un cercle. Le title et l'aria-label portent alors
     seuls le sens, d'ou le libelle conserve pour les lecteurs d'ecran. */
  .user-chip .chip-logout-label {
    /* display explicite : .user-chip span pose display:none juste au-dessus.
       Cette regle gagne sur width et height, mais sans declarer display elle
       laissait le libelle hors de l'arbre d'accessibilite. */
    display: inline;
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* .btn.small (0,2,0) l'emporterait sur .chip-logout (0,1,0) et imposerait
     son padding lateral de 15px : le bouton restait ovale. On egalise la
     specificite pour obtenir un cercle. */
  .user-chip .chip-logout { padding: 7px; gap: 0; }
}

/* ---------------------------------------------------------------- admin */

.admin-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  margin-bottom: 18px;
}

.admin-counts { display: flex; gap: 9px; flex-wrap: wrap; }

/* Recherche de commandes ------------------------------------------------- */

.admin-tools {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.order-search {
  position: relative;
  display: flex;
  align-items: center;
}

.order-search-icon {
  position: absolute;
  left: 11px;
  width: 16px;
  height: 16px;
  color: var(--v-300);
  pointer-events: none;
  transition: color 0.18s;
}

.order-search input {
  width: 260px;
  max-width: 100%;
  padding: 9px 34px 9px 34px;
  border-radius: 11px;
  border: 1px solid var(--border);
  background: rgba(124, 58, 237, 0.1);
  color: var(--text);
  font: inherit;
  font-size: 13px;
  transition: border-color 0.18s, background 0.18s, box-shadow 0.18s, width 0.22s;
}

.order-search input::placeholder { color: var(--muted); }

/* La croix native de `type="search"` doublonne avec notre bouton. */
.order-search input::-webkit-search-decoration,
.order-search input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }

.order-search input:focus {
  outline: none;
  border-color: var(--accent);
  background: rgba(124, 58, 237, 0.16);
  box-shadow: 0 0 0 3px rgba(217, 70, 239, 0.16);
}

.order-search:focus-within .order-search-icon { color: var(--accent); }

.order-search-clear {
  position: absolute;
  right: 7px;
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--muted);
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
  transition: color 0.18s, background 0.18s;
}

.order-search-clear:hover { color: var(--text); background: rgba(217, 70, 239, 0.2); }
.order-search-clear[hidden] { display: none; }

@media (max-width: 720px) {
  .admin-tools { width: 100%; }
  .order-search { flex: 1 1 auto; }
  .order-search input { width: 100%; }
}

.admin-note {
  margin-bottom: 16px;
  padding: 12px 15px;
  border-radius: 12px;
  background: rgba(251, 191, 36, 0.1);
  border: 1px solid rgba(251, 191, 36, 0.3);
  color: #fde68a;
  font-size: 13px;
}

.role-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border: 1px solid var(--border);
  background: rgba(124, 58, 237, 0.14);
  color: var(--v-100);
}

.role-pill.owner {
  background: linear-gradient(135deg, rgba(217, 70, 239, 0.3), rgba(124, 58, 237, 0.3));
  border-color: var(--accent);
  color: #fae8ff;
}

.role-pill.admin { background: rgba(124, 58, 237, 0.24); border-color: var(--v-300); }
.role-pill.user  { background: rgba(10, 4, 19, 0.5); color: var(--muted); }

.role-tag {
  padding: 3px 9px;
  border-radius: 999px;
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  background: rgba(124, 58, 237, 0.28);
  border: 1px solid var(--border);
  color: var(--v-100);
}

.role-tag.owner {
  background: linear-gradient(135deg, var(--v-400), var(--accent));
  border-color: transparent;
  color: #fff;
}

/* Etiquette owner dans le header. Les selecteurs sont prefixes par .user-chip
   pour passer devant la regle generique .user-chip span (0,1,1) : sans cela
   la taille et la couleur du texte seraient celles du pseudo. */
.user-chip .role-tag {
  /* Chaque rang decline la meme parure -- fond transparent, contour teinte,
     lumiere diagonale -- en changeant seulement ces quatre variables. Valeurs
     par defaut : l'ardoise neutre du rang « user », sans formule. */
  --tag-tint: rgba(148, 163, 184, 0.12);   /* voile de fond */
  --tag-edge: rgba(148, 163, 184, 0.5);    /* contour */
  --tag-ink: #e8eef7;                      /* libelle */
  --tag-glow: rgba(148, 163, 184, 0.3);    /* halo et ombre du texte */

  position: relative;
  overflow: hidden;            /* la lumiere reste dans la forme arrondie */
  text-shadow: 0 1px 6px var(--tag-glow);
  box-shadow: 0 0 14px var(--tag-glow);

  /* inline-flex pour aligner la couronne sur le libelle : en inline-block, le
     SVG se posait sur la ligne de base et pendait sous le texte. */
  display: inline-flex;
  align-items: center;
  /* 4px et non 5 : l'emoji a remplace la couronne sur tous les rangs et a
     elargi chaque etiquette. Les glyphes portant deja un blanc tournant dans
     leur chasse, ce cran resserre l'ecart sans le faire paraitre etroit. */
  gap: 4px;
  font-family: "Outfit", system-ui, sans-serif;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.1em;    /* l'interlettrage aere les capitales */
  padding: 5px 12px;        /* la regle de base .role-tag en pose 3px / 9px */
  max-width: none;          /* la regle generique plafonne a 170px */
  flex: 0 0 auto;           /* l'etiquette ne doit pas se comprimer */
}

/* Le libelle est un span, donc capte par la regle generique .user-chip span
   (taille et couleur du pseudo, et display:none en ecran etroit). Il doit au
   contraire suivre son etiquette : on annule cet heritage. */
.user-chip .role-tag .role-label {
  display: inline;
  font: inherit;
  color: inherit;
  max-width: none;
  overflow: visible;
}

/* Emoji du rang. Comme le libelle, il est capte par la regle generique
   .user-chip span : il faut donc annuler cet heritage, sans quoi il prendrait
   la taille du pseudo et disparaitrait en ecran etroit. */
.user-chip .role-tag .role-emoji {
  display: inline-block;
  /* Police dediee : sans elle, le rendu retombe sur Outfit qui n'a aucun
     glyphe emoji, et le systeme choisit une substitution imprevisible. */
  font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
  /* Un cran sous le libelle : a taille egale, l'emoji plein pese davantage a
     l'oeil que des capitales fines et prendrait le dessus sur le nom du rang. */
  font-size: 10px;
  line-height: 1;
  max-width: none;
  overflow: visible;
  /* Les glyphes emoji portent leur propre chasse : ce leger retrait evite que
     l'ecart avec le libelle paraisse plus large que le gap declare. */
  margin-right: -1px;
  /* Detache l'emoji du fond translucide, comme le faisait la couronne. */
  filter: drop-shadow(0 1px 2px rgba(10, 4, 19, 0.6));
}

/* Teintes par rang. Le fond reste transparent partout : l'etiquette laisse
   voir la pastille au lieu de poser un bloc de couleur pleine, et c'est le
   contour qui porte l'identite du rang. Hors du header, les rangs gardent
   leur pastille pleine d'origine.

   Specificite : plus bas dans le fichier, .role-tag.pro et .role-tag.illimite
   posent un degrade opaque. Elles pesent 0,2,0, autant que la regle de base
   .user-chip .role-tag, et sont declarees apres : elles gagneraient. Les
   proprietes qui lisent les variables sont donc portees ici, sur des
   selecteurs a trois classes (0,3,0), qui passent devant sans !important.
   Le rang « user » n'a ni bloc dedie ni regle legacy : il est couvert par la
   regle a deux classes ci-dessous. */
.user-chip .role-tag,
.user-chip .role-tag[class] {
  background: var(--tag-tint);
  border-color: var(--tag-edge);
  color: var(--tag-ink);
}

.user-chip .role-tag.decouverte,
.account-role.decouverte {
  --tag-tint: rgba(148, 163, 184, 0.12);
  --tag-edge: rgba(203, 213, 225, 0.55);
  --tag-ink: #eef2f7;
  --tag-glow: rgba(148, 163, 184, 0.34);
}

/* Cyan franc plutot que bleu ciel : contre l'admin, voisin dans le bleu, deux
   teintes trop proches se confondaient d'un affichage a l'autre. */
.user-chip .role-tag.starter,
.account-role.starter {
  --tag-tint: rgba(34, 211, 238, 0.12);
  --tag-edge: rgba(103, 232, 249, 0.6);
  --tag-ink: #ddf8ff;
  --tag-glow: rgba(34, 211, 238, 0.34);
}

/* Indigo et non violet : l'owner occupe deja le violet, et deux contours de la
   meme famille rendaient le rang payant indistinct du rang suprême. */
.user-chip .role-tag.pro,
.account-role.pro {
  --tag-tint: rgba(99, 102, 241, 0.12);
  --tag-edge: rgba(129, 140, 248, 0.55);
  --tag-ink: #e8e7ff;
  --tag-glow: rgba(99, 102, 241, 0.34);
}

.user-chip .role-tag.business,
.account-role.business {
  --tag-tint: rgba(251, 191, 36, 0.12);
  --tag-edge: rgba(252, 211, 77, 0.55);
  --tag-ink: #fff4d6;
  --tag-glow: rgba(251, 191, 36, 0.32);
}

.user-chip .role-tag.illimite,
.account-role.illimite {
  --tag-tint: rgba(45, 212, 191, 0.12);
  --tag-edge: rgba(94, 234, 212, 0.55);
  --tag-ink: #dcfff6;
  --tag-glow: rgba(45, 212, 191, 0.32);
}

.user-chip .role-tag.admin,
.account-role.admin {
  --tag-tint: rgba(96, 165, 250, 0.12);
  --tag-edge: rgba(147, 197, 253, 0.55);
  --tag-ink: #e6f0ff;
  --tag-glow: rgba(96, 165, 250, 0.34);
}

.user-chip .role-tag.owner,
.account-role.owner {
  --tag-tint: rgba(168, 85, 247, 0.12);
  --tag-edge: rgba(192, 132, 252, 0.55);
  /* Sans fond sature, le blanc pur devient dur : on garde la lavande tres
     claire du theme, plus douce sur le violet sombre de la pastille. */
  --tag-ink: #f5ebff;
  --tag-glow: rgba(217, 70, 239, 0.45);
}

/* Le rang le plus haut garde en propre sa pulsation de halo : c'est ce qui le
   distingue des autres, qui ont un halo fixe. Reserve a la barre : dans la
   fiche, l'etiquette est posee une fois sous les yeux, une pulsation continue
   y serait une agitation sans motif. */
.user-chip .role-tag.owner {
  box-shadow: 0 0 14px rgba(168, 85, 247, 0.18);
  animation: owner-glow 3s ease-in-out infinite;
}

/* Lumiere qui traverse en diagonale, du coin haut droit vers le coin bas
   gauche. Le degrade est oriente a 225deg (l'axe du deplacement) et la bande
   claire progresse en deplacant le fond d'un coin a l'autre. Commune a tous
   les rangs : c'est la signature de l'etiquette dans la barre. */
.user-chip .role-tag::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(225deg,
    transparent 38%,
    /* 0.42 et non 0.5 : au moment ou la crete passe sous le libelle, le fond
       s'eclaircit et le contraste tombe a son plancher. Cette valeur garde la
       lumiere franchement visible tout en laissant de la marge sur le seuil
       de lisibilite. */
    rgba(245, 235, 255, 0.42) 50%,
    transparent 62%);
  background-size: 260% 260%;
  animation: owner-light 4.5s ease-in-out infinite;
  pointer-events: none;
}

/* Pause en fin de course : la lumiere passe, puis l'etiquette reste calme un
   instant avant le passage suivant. Un balayage continu fatiguerait l'oeil
   dans une barre toujours affichee.

   Attention au sens : background-position deplace la fenetre de vue sur le
   fond, pas le fond lui-meme. La bande claire se deplace donc a l'inverse des
   valeurs. Pour la faire aller du coin haut droit vers le coin bas gauche, il
   faut partir de 0% 100% et aller vers 100% 0%. */
@keyframes owner-light {
  0%        { background-position: 0% 100%; }
  55%, 100% { background-position: 100% 0%; }
}

/* L'ancien reflet balaye (::after) a ete retire avec le passage au fond
   transparent : il n'avait plus de bloc opaque a eclairer, et l'attenuer assez
   pour ne pas voiler la pastille le rendait imperceptible. Le contour degrade
   qui glisse du violet vers le dore porte desormais l'effet a lui seul, pour
   une animation permanente au lieu de deux. */

/* Le libelle et la couronne passent au-dessus de la lumiere : le pseudo-element
   se peint apres le contenu et le voilerait sinon. */
.user-chip .role-tag > * {
  position: relative;
  z-index: 1;
}

/* Pulsation attenuee : sur fond transparent, l'ancienne lueur a 0.8 formait un
   halo violet plus visible que l'etiquette elle-meme. */
@keyframes owner-glow {
  0%, 100% { box-shadow: 0 0 12px rgba(124, 58, 237, 0.16); }
  50%      { box-shadow: 0 0 20px rgba(217, 70, 239, 0.34); }
}

/* La pastille entiere se pare de violet quand le compte est owner. */
.user-chip.is-owner {
  border-color: rgba(168, 85, 247, 0.6);
  background: linear-gradient(135deg, rgba(76, 29, 149, 0.55), rgba(10, 4, 19, 0.65));
  box-shadow: 0 0 0 1px rgba(192, 132, 252, 0.14), 0 6px 24px rgba(124, 58, 237, 0.28);
}

.user-table-wrap {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  backdrop-filter: blur(18px);
  overflow-x: auto;
}

.user-table { width: 100%; border-collapse: collapse; min-width: 620px; }

.user-table th {
  padding: 14px 18px;
  text-align: left;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--v-200);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}

.user-table td {
  padding: 14px 18px;
  font-size: 13.5px;
  border-bottom: 1px solid rgba(168, 85, 247, 0.1);
  vertical-align: middle;
}

.user-table tr:last-child td { border-bottom: none; }
.user-table tbody tr { transition: background 0.18s; }
.user-table tbody tr:hover { background: rgba(124, 58, 237, 0.08); }

.user-table .row-disabled { opacity: 0.5; }

.cell-mail { word-break: break-word; }

.cell-name {
  font-weight: 600;
  font-size: 14px;
  color: var(--text);
  display: flex;
  align-items: center;
  gap: 4px;
  flex-wrap: wrap;
}

.cell-submail {
  margin-top: 3px;
  font-size: 11.5px;
  color: var(--muted);
  word-break: break-all;
}
.cell-date { color: var(--muted); font-size: 12.5px; white-space: nowrap; }

.self-badge {
  margin-left: 8px;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  background: rgba(74, 222, 128, 0.16);
  border: 1px solid rgba(74, 222, 128, 0.35);
  color: var(--ok);
}

.state-tag {
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 11.5px;
  font-weight: 500;
  white-space: nowrap;
}

.state-tag.on   { background: rgba(74, 222, 128, 0.14); color: var(--ok); border: 1px solid rgba(74, 222, 128, 0.3); }
.state-tag.off  { background: rgba(251, 113, 133, 0.14); color: var(--err); border: 1px solid rgba(251, 113, 133, 0.3); }
.state-tag.warn { background: rgba(251, 191, 36, 0.14); color: var(--warn); border: 1px solid rgba(251, 191, 36, 0.3); }

.role-select {
  width: auto;
  min-width: 170px;
  padding: 7px 11px;
  font-size: 12.5px;
  letter-spacing: 0.02em;
  font-weight: 700;
}

.user-table td .btn { margin-right: 7px; }
.user-table td .btn:last-child { margin-right: 0; }

.user-table .empty { text-align: center; color: var(--muted); padding: 30px; }
.user-table .dash { color: rgba(167, 139, 250, 0.4); }

@media (max-width: 720px) {
  .admin-bar { flex-direction: column; align-items: stretch; }
}

.field-hint {
  display: block;
  margin-top: 6px;
  font-size: 11.5px;
  color: var(--muted);
  transition: color 0.18s;
}

.field-hint.good { color: var(--ok); }
.field-hint.bad  { color: var(--err); }

/* ------------------------------------------------- connexion / inscription */

.auth-body { justify-content: center; }

.auth-wrap {
  position: relative;
  z-index: 1;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 40px 20px;
}

.auth-card {
  width: 100%;
  max-width: 420px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 22px;
  padding: 34px 30px 26px;
  backdrop-filter: blur(20px);
  box-shadow: 0 26px 70px rgba(0, 0, 0, 0.55), var(--glow);
  animation: auth-in 0.45s cubic-bezier(0.2, 0.9, 0.3, 1.1) both;
}

@keyframes auth-in {
  from { opacity: 0; transform: translateY(18px) scale(0.97); }
  to   { opacity: 1; transform: none; }
}

.auth-head { text-align: center; margin-bottom: 24px; }
/* Page de connexion : logo centre et un peu plus grand, la carte est aeree. */
.auth-head .brand-mark { width: 56px; height: 56px; margin: 0 auto 14px; }
.auth-head .logo { font-size: 30px; margin-bottom: 8px; }

.auth-sub { font-size: 13.5px; color: var(--muted); line-height: 1.6; }

.auth-tabs {
  display: flex;
  gap: 6px;
  padding: 5px;
  margin-bottom: 22px;
  border-radius: 13px;
  background: rgba(10, 4, 19, 0.6);
  border: 1px solid var(--border);
}

.auth-tab {
  flex: 1;
  padding: 10px;
  border: none;
  border-radius: 9px;
  background: transparent;
  color: var(--muted);
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.2s, color 0.2s;
}

.auth-tab.active {
  background: linear-gradient(135deg, var(--v-400), var(--accent));
  color: #fff;
  font-weight: 700;
  box-shadow: 0 5px 18px rgba(124, 58, 237, 0.4);
}

/* ------------------------------------- connexion par compte externe */

.oauth-zone { margin-bottom: 20px; }

.oauth-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px;
}

.oauth-btn {
  --teinte: 124, 58, 237;
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 13px 14px;
  border-radius: 13px;
  border: 1px solid rgba(var(--teinte), 0.4);
  background:
    linear-gradient(180deg, rgba(var(--teinte), 0.18), rgba(var(--teinte), 0.06));
  color: var(--v-100);
  font-size: 13.5px;
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
  transition: transform 0.2s cubic-bezier(0.2, 0.9, 0.3, 1.2), border-color 0.2s, box-shadow 0.25s, background 0.25s;
  /* Apparition decalee : les boutons se posent l'un apres l'autre plutot que
     d'un bloc, ce qui accroche l'oeil sans ralentir la saisie. */
  animation: oauth-in 0.5s cubic-bezier(0.2, 0.9, 0.3, 1.1) both;
  animation-delay: calc(var(--i, 0) * 90ms);
}

@keyframes oauth-in {
  from { opacity: 0; transform: translateY(10px) scale(0.96); }
  to   { opacity: 1; transform: none; }
}

/* Halo qui balaie le bouton au survol. Place en ::before pour ne pas
   perturber la mise en page du logo et du texte. */
.oauth-btn::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(115deg, transparent 30%, rgba(255, 255, 255, 0.22), transparent 70%);
  transform: translateX(-120%);
  transition: transform 0.6s ease;
  pointer-events: none;
}

.oauth-btn:hover {
  transform: translateY(-2px);
  border-color: rgba(var(--teinte), 0.75);
  background: linear-gradient(180deg, rgba(var(--teinte), 0.32), rgba(var(--teinte), 0.14));
  box-shadow: 0 12px 28px rgba(var(--teinte), 0.32);
}

.oauth-btn:hover::before { transform: translateX(120%); }
.oauth-btn:active { transform: translateY(0) scale(0.985); }

.oauth-btn:focus-visible {
  outline: none;
  border-color: rgba(var(--teinte), 0.9);
  box-shadow: 0 0 0 3px rgba(var(--teinte), 0.32);
}

/* Teintes propres a chaque marque, gardees discretes pour rester dans le
   violet du panneau : la couleur ne vit que dans le logo et le halo. */
.oauth-btn.discord { --teinte: 88, 101, 242; }
.oauth-btn.google  { --teinte: 152, 110, 245; }

.oauth-logo {
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  flex: none;
  transition: transform 0.3s cubic-bezier(0.2, 0.9, 0.3, 1.4);
}

.oauth-logo svg { width: 100%; height: 100%; display: block; }
/* Le logo Discord est monochrome : il prend la couleur de la marque. */
.oauth-btn.discord .oauth-logo { color: #fff; }
.oauth-btn:hover .oauth-logo { transform: scale(1.14) rotate(-6deg); }

.oauth-label { letter-spacing: 0.01em; }

/* Séparateur "ou avec ton e-mail" : deux filets de part et d'autre du texte. */
.oauth-sep {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 18px;
}

.oauth-sep::before,
.oauth-sep::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--border), transparent);
}

.oauth-sep span {
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(167, 139, 250, 0.6);
  white-space: nowrap;
}

/* Mouvement reduit demande par le systeme : on garde la couleur, on retire
   les deplacements. */
@media (prefers-reduced-motion: reduce) {
  .oauth-btn { animation: none; transition: border-color 0.2s, background 0.2s; }
  .oauth-btn:hover { transform: none; }
  .oauth-btn::before { display: none; }
  .oauth-btn:hover .oauth-logo { transform: none; }
}

/* ------------------------------------- afficher / masquer le mot de passe */

.pwd-wrap { position: relative; }

/* Reserve la place de l'oeil : sans cela un mot de passe long passerait
   dessous, et le point de la derniere lettre deviendrait illisible. */
.pwd-wrap input { padding-right: 46px; }

.pwd-eye {
  position: absolute;
  top: 50%;
  right: 6px;
  transform: translateY(-50%);
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  padding: 0;
  border: none;
  border-radius: 9px;
  background: transparent;
  color: rgba(167, 139, 250, 0.65);
  cursor: pointer;
  transition: color 0.18s, background 0.18s;
}

.pwd-eye svg { width: 19px; height: 19px; display: block; }

.pwd-eye:hover { color: var(--v-100); background: rgba(124, 58, 237, 0.16); }

/* Mot de passe affiche : la teinte pleine rappelle qu'il est en clair. */
.pwd-eye.on { color: var(--accent); }

.pwd-eye:focus-visible {
  outline: none;
  color: var(--v-100);
  box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.45);
}

/* Chrome ajoute son propre oeil sur les champs mot de passe : sans cela, deux
   icones se superposeraient. */
.pwd-wrap input::-ms-reveal,
.pwd-wrap input::-ms-clear { display: none; }

/* ------------------------------- accueil du nouvel arrivant (pseudo) */

.pseudo-welcome {
  text-align: center;
  margin-bottom: 20px;
  animation: oauth-in 0.5s cubic-bezier(0.2, 0.9, 0.3, 1.1) both;
}

.pseudo-badge {
  --teinte: 124, 58, 237;
  display: inline-block;
  margin-bottom: 12px;
  padding: 6px 14px;
  border-radius: 999px;
  border: 1px solid rgba(var(--teinte), 0.45);
  background: rgba(var(--teinte), 0.16);
  color: var(--v-100);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  /* Respiration lente : marque l'arrivee sans distraire de la saisie. */
  animation: pseudo-pulse 2.6s ease-in-out infinite;
}

.pseudo-badge.discord { --teinte: 88, 101, 242; }
.pseudo-badge.google  { --teinte: 152, 110, 245; }

@keyframes pseudo-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(var(--teinte), 0.35); }
  50%      { box-shadow: 0 0 0 7px rgba(var(--teinte), 0); }
}

.pseudo-welcome .auth-hint { margin: 0; }

@media (prefers-reduced-motion: reduce) {
  .pseudo-welcome, .pseudo-badge { animation: none; }
}

/* Les formulaires inactifs sortent du flux : sinon leurs champs "required"
   bloqueraient la validation du formulaire visible. */
.auth-form { display: none; }
.auth-form.active { display: block; }

.auth-submit { width: 100%; margin-top: 4px; }

.auth-link {
  width: 100%;
  margin-top: 10px;
  background: transparent;
  border-color: transparent;
  color: var(--muted);
}

.auth-link:hover:not(:disabled) { color: var(--v-100); background: rgba(124, 58, 237, 0.14); }

.auth-hint {
  font-size: 13px;
  color: var(--muted);
  line-height: 1.6;
  margin-bottom: 18px;
  padding: 12px 14px;
  border-radius: 12px;
  background: rgba(124, 58, 237, 0.12);
  border: 1px solid var(--border);
}

.auth-foot {
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid var(--border);
  font-size: 11.5px;
  color: rgba(167, 139, 250, 0.62);
  text-align: center;
  line-height: 1.6;
}

.code-input {
  text-align: center;
  font-family: "JetBrains Mono", monospace;
  font-size: 26px;
  font-weight: 700;
  letter-spacing: 12px;
  padding: 15px 14px;
  text-indent: 12px;
}

/* --------------------------------------------------------- force du mot de passe */

.pwd-meter {
  height: 4px;
  margin-top: 9px;
  border-radius: 999px;
  background: rgba(10, 4, 19, 0.7);
  overflow: hidden;
}

.pwd-meter span {
  display: block;
  width: 0;
  height: 100%;
  border-radius: 999px;
  transition: width 0.28s, background 0.28s;
}

.pwd-meter span.weak   { background: var(--err); }
.pwd-meter span.medium { background: var(--warn); }
.pwd-meter span.strong { background: var(--ok); }

.pwd-rules {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 6px 12px;
  margin-top: 10px;
}

.pwd-rules li {
  font-size: 11.5px;
  color: rgba(167, 139, 250, 0.66);
  transition: color 0.2s;
}

.pwd-rules li::before {
  content: "○ ";
  font-size: 10px;
}

.pwd-rules li.ok { color: var(--ok); }
.pwd-rules li.ok::before { content: "● "; }

/* -------------------------------------------------------------- boutique */

.promo-bar {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  padding: 13px 20px;
  margin-bottom: 26px;
  border-radius: 14px;
  background: linear-gradient(100deg, rgba(217, 70, 239, 0.2), rgba(124, 58, 237, 0.14));
  border: 1px solid var(--v-500);
}

.promo-bar p { font-size: 13.5px; color: var(--v-100); }
.promo-bar b { font-family: "JetBrains Mono", monospace; color: #fff; }

.promo-tag {
  padding: 5px 12px;
  border-radius: 999px;
  font-size: 12.5px;
  font-weight: 900;
  letter-spacing: 0.04em;
  color: #fff;
  background: linear-gradient(135deg, var(--v-400), var(--accent));
  box-shadow: 0 4px 16px rgba(217, 70, 239, 0.5);
}

.shop-head { text-align: center; }
.shop-head p { margin: 0 auto; }

.trust-row {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 14px;
  margin-bottom: 30px;
}

.trust-item {
  text-align: center;
  padding: 16px 10px;
  border-radius: 14px;
  background: rgba(124, 58, 237, 0.1);
  border: 1px solid var(--border);
}

.trust-item b {
  display: block;
  font-size: 22px;
  font-weight: 900;
  color: var(--v-100);
  font-variant-numeric: tabular-nums;
}

.trust-item span {
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

@media (max-width: 760px) {
  .trust-row { grid-template-columns: repeat(2, 1fr); }
}

/* Cartes tarifaires : la formule mise en avant depasse volontairement des
   autres pour guider le regard sans texte supplementaire. */
.plans {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 14px;
  align-items: stretch;
  margin-bottom: 16px;
}

@media (max-width: 1100px) { .plans { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 760px)  { .plans { grid-template-columns: 1fr; } }

.plan {
  display: flex;
  flex-direction: column;
  padding: 26px 20px 22px;
  border-radius: var(--radius);
  background: var(--panel);
  border: 1px solid var(--border);
  backdrop-filter: blur(18px);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.4);
  position: relative;
  transition: transform 0.22s, border-color 0.22s, box-shadow 0.22s;
}

.plan:hover {
  transform: translateY(-6px);
  border-color: var(--v-300);
  box-shadow: 0 26px 62px rgba(124, 58, 237, 0.32);
}

.plan.featured {
  border-color: var(--accent);
  background: linear-gradient(170deg, rgba(217, 70, 239, 0.16), var(--panel) 55%);
  box-shadow: 0 22px 60px rgba(217, 70, 239, 0.3);
}

@media (min-width: 1101px) {
  .plan.featured { transform: scale(1.045); z-index: 2; }
  .plan.featured:hover { transform: scale(1.045) translateY(-6px); }
}

.plan-flag {
  position: absolute;
  top: -11px; left: 50%;
  transform: translateX(-50%);
  padding: 5px 14px;
  border-radius: 999px;
  font-size: 10.5px;
  font-weight: 900;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
  color: #fff;
  background: linear-gradient(135deg, var(--v-400), var(--accent));
  box-shadow: 0 5px 18px rgba(217, 70, 239, 0.6);
}

.plan-name { font-size: 18px; font-weight: 900; margin-bottom: 4px; }
.plan-tagline { font-size: 12.5px; color: var(--muted); line-height: 1.5; min-height: 38px; }

.plan-price {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 5px;
  margin: 12px 0 4px;
}

.plan-old {
  width: 100%;
  font-size: 13px;
  color: rgba(167, 139, 250, 0.55);
  text-decoration: line-through;
}

.plan-amount {
  font-size: 44px;
  font-weight: 900;
  letter-spacing: -0.04em;
  line-height: 1;
  background: linear-gradient(96deg, var(--v-100), var(--v-300) 55%, var(--accent));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  font-variant-numeric: tabular-nums;
}

.plan-cur { font-size: 20px; font-weight: 700; color: var(--v-200); }

/* La periodicite reste discrete a cote du prix : elle doit se lire, sans
   concurrencer le chiffre qui porte l'information principale. */
.plan-per { font-size: 13px; font-weight: 600; color: var(--v-200); opacity: 0.75; margin-left: 2px; }
.plan-once { font-size: 11.5px; color: var(--ok); margin-bottom: 16px; }

.plan-meta {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 12px 0;
  margin-bottom: 14px;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  font-size: 13px;
  font-weight: 600;
  color: var(--v-100);
}

.plan-perks { list-style: none; display: flex; flex-direction: column; gap: 8px; margin-bottom: 20px; }

.plan-perks li {
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--muted);
  padding-left: 20px;
  position: relative;
}

.plan-perks li::before {
  content: "✓";
  position: absolute;
  left: 0;
  font-weight: 900;
  color: var(--ok);
}

/* Pousse le bouton en bas quelle que soit la longueur de la liste. */
.plan-btn { margin-top: auto; width: 100%; }

.plans-note {
  text-align: center;
  font-size: 12.5px;
  color: var(--muted);
  margin-bottom: 46px;
}

/* --------------------------------------------------- comparatif / preuves */

.compare-title {
  font-size: 24px;
  font-weight: 900;
  letter-spacing: -0.02em;
  margin-bottom: 18px;
}

.compare-wrap { margin-bottom: 46px; }

.compare-scroll {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  backdrop-filter: blur(18px);
  overflow-x: auto;
}

.compare-table { width: 100%; border-collapse: collapse; min-width: 760px; }

.compare-table th, .compare-table td {
  padding: 14px 16px;
  font-size: 13px;
  text-align: center;
  border-bottom: 1px solid rgba(168, 85, 247, 0.1);
}

.compare-table thead th {
  font-size: 13.5px;
  font-weight: 900;
  color: var(--v-100);
  border-bottom: 1px solid var(--border);
}

.compare-table thead th em {
  display: block;
  margin-top: 3px;
  font-style: normal;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--muted);
}

.compare-table tbody th {
  text-align: left;
  font-weight: 500;
  color: var(--muted);
  white-space: nowrap;
}

.compare-table tbody tr:last-child th,
.compare-table tbody tr:last-child td { border-bottom: none; }

.compare-table tbody tr:hover { background: rgba(124, 58, 237, 0.07); }

.compare-table .col-featured { background: rgba(217, 70, 239, 0.09); }
.compare-table .yes { color: var(--ok); font-weight: 900; }
.compare-table .no  { color: rgba(167, 139, 250, 0.35); }

.quotes {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
  margin-bottom: 46px;
}

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

.quote {
  padding: 24px;
  border-radius: var(--radius);
  background: var(--panel);
  border: 1px solid var(--border);
  backdrop-filter: blur(18px);
  transition: transform 0.2s, border-color 0.2s;
}

.quote:hover { transform: translateY(-4px); border-color: var(--v-300); }

.quote blockquote {
  font-size: 14px;
  line-height: 1.7;
  color: var(--v-100);
  margin-bottom: 16px;
}

.quote figcaption { display: flex; flex-direction: column; gap: 2px; }
.quote figcaption b { font-size: 13.5px; }
.quote figcaption span { font-size: 11.5px; color: var(--muted); }

.faq { margin-bottom: 46px; }

.faq-item {
  padding: 16px 20px;
  margin-bottom: 10px;
  border-radius: 14px;
  background: var(--panel);
  border: 1px solid var(--border);
  backdrop-filter: blur(18px);
}

.faq-item summary {
  cursor: pointer;
  font-size: 14.5px;
  font-weight: 600;
  list-style: none;
  display: flex;
  justify-content: space-between;
  gap: 14px;
}

.faq-item summary::-webkit-details-marker { display: none; }

.faq-item summary::after {
  content: "+";
  font-size: 20px;
  font-weight: 300;
  line-height: 1;
  color: var(--v-300);
  transition: transform 0.2s;
}

.faq-item[open] summary::after { transform: rotate(45deg); }

.faq-item p {
  margin-top: 12px;
  font-size: 13.5px;
  line-height: 1.7;
  color: var(--muted);
}

/* ------------------------------------------------------ modale de commande */

.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  padding: 20px;
  /* Filet de securite : si malgre tout le contenu depasse (zoom eleve,
     traduction automatique qui rallonge les textes), le fond defile au lieu
     de rendre le bas de la fenetre inatteignable. */
  overflow-y: auto;
  background: rgba(5, 2, 10, 0.72);
  backdrop-filter: blur(6px);
  animation: fade-in 0.2s ease both;
}

/* [hidden] seul ne suffirait pas : display:grid l'emporterait. */
.modal-backdrop[hidden] { display: none; }

@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }

.modal {
  position: relative;
  width: 100%;
  max-width: 420px;
  padding: 30px 28px 26px;
  border-radius: 22px;
  background: linear-gradient(170deg, rgba(42, 21, 83, 0.96), rgba(18, 8, 37, 0.97));
  border: 1px solid var(--v-500);
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.6), var(--glow);
  animation: auth-in 0.34s cubic-bezier(0.2, 0.9, 0.3, 1.1) both;
  /* Ne depasse jamais de l'ecran, quelle que soit la hauteur de la fenetre :
     le contenu s'adapte au lieu de sortir par le bas. La ligne vh sert de
     repli aux navigateurs anciens ; dvh la remplace ensuite, car la barre
     d'adresse mobile qui se retracte fausse le calcul en vh. */
  max-height: calc(100vh - 40px);
  max-height: calc(100dvh - 40px);
  display: flex;
  flex-direction: column;
}

/* Fenetres a hauteur libre : leur contenu gere deja son propre defilement, et
   les contraindre ici casserait leur mise en page. */
.pay-backdrop .modal, .ask-sheet { max-height: none; display: block; }

/* Corps defilant : le titre et les boutons restent en vue, seul le detail
   glisse quand la place manque. */
.modal-body {
  flex: 1 1 auto;
  min-height: 0;               /* sans quoi un enfant flex refuse de retrecir */
  overflow-y: auto;
  /* Marge negative puis padding : la barre de defilement se place au bord de
     la fenetre plutot que de rogner le texte. */
  margin: 0 -28px;
  padding: 0 28px;
  overscroll-behavior: contain; /* le defilement ne se propage pas a la page */
}

.modal-foot { flex: none; padding-top: 4px; }

/* Barre discrete, dans la teinte du panneau. */
.modal-body::-webkit-scrollbar { width: 8px; }
.modal-body::-webkit-scrollbar-track { background: transparent; }
.modal-body::-webkit-scrollbar-thumb {
  background: rgba(124, 58, 237, 0.4);
  border-radius: 8px;
}
.modal-body::-webkit-scrollbar-thumb:hover { background: rgba(168, 85, 247, 0.6); }
.modal-body { scrollbar-width: thin; scrollbar-color: rgba(124, 58, 237, 0.5) transparent; }

.modal h3 { font-size: 23px; font-weight: 900; margin-bottom: 20px; }

/* Fenetre courte (ordinateur portable, navigateur charge de barres d'outils) :
   on resserre les espacements pour garder le maximum de contenu visible.
   Declare apres .modal h3, sinon la regle de base l'emporterait. */
@media (max-height: 700px) {
  .modal { padding: 24px 24px 20px; }
  .modal-body { margin: 0 -24px; padding: 0 24px; }
  .modal h3 { font-size: 20px; margin-bottom: 14px; }
  /* Le padding se resserre a 24px : la croix suit, pour rester alignee sur la
     pastille. La fenetre de paiement garde le sien, gere plus bas. */
  .modal:not(.pay-sheet) > .modal-close { top: 22px; right: 14px; }
  .buy-lines { margin-bottom: 12px; }
  .buy-line { padding: 8px 0; }
  .buy-note { margin-bottom: 12px; }
  .buy-step { margin-bottom: 12px; }
}

/* .modal est un conteneur flex en colonne : ses enfants directs deviennent des
   elements flex, et display:inline-block n'y a plus d'effet. La pastille
   "recapitulatif" s'etirait donc sur toute la largeur et passait sous la
   croix. align-self la ramene a la largeur de son texte, et la marge droite
   reserve la place du bouton de fermeture. */
.modal > .eyebrow {
  align-self: flex-start;
  margin-right: 44px;
}

/* La croix doit passer au-dessus de tout le contenu de la fenetre. Sans
   z-index elle se retrouve sous les elements positionnes qui la suivent (le
   bandeau du paiement et son halo), qui interceptent alors les clics : le
   bouton parait mort alors qu'il n'est jamais atteint. */
.modal-close {
  position: absolute;
  z-index: 20;
  /* Centree sur la pastille qui lui fait face, plutot que collee au coin.
     Vertical : 30px de padding + 14px (demi-hauteur de la pastille, ~28px)
     - 16px (demi-hauteur du bouton) = 28px.
     Horizontal : le bouton fait 32px pour la zone de clic, mais le signe qu'il
     contient n'en occupe que ~11px au centre. On recule donc de la moitie de
     l'ecart ((32-11)/2 ~ 10px) pour que le signe, et non la boite invisible,
     s'aligne sur le bord droit du contenu (padding de 28px). */
  top: 28px; right: 18px;
  width: 32px; height: 32px;
  display: grid;
  place-items: center;
  border: none;
  border-radius: 10px;
  background: transparent;
  color: var(--muted);
  font-size: 24px;
  line-height: 1;
  cursor: pointer;
  transition: color 0.18s ease;
}

/* Seule la couleur change au survol : pas de pastille ni de rotation, qui
   alourdissaient le coin de la fenetre. */
.modal-close:hover { color: #fff; }

/* La fenetre de paiement n'a pas de padding propre : c'est son bandeau qui
   porte l'espacement (26px en haut, 30px sur les cotes). La croix s'aligne sur
   la pastille de reference. Le calcul theorique donnait 21px, mais la mesure au
   navigateur montrait la croix 2px trop haute : la pastille du bandeau est un
   peu plus haute que sa hauteur nominale. Valeur corrigee d'apres le rendu
   reel. Meme correction horizontale que plus haut : 30px - 10px. */
.pay-sheet > .modal-close { top: 23px; right: 20px; }

/* -------- confirmation maison -------- */

/* Passe au-dessus des autres fenetres : elle est toujours ouverte par-dessus
   l'une d'elles. */
.ask-backdrop { z-index: 120; padding: 20px; }

.ask-sheet {
  position: relative;
  overflow: hidden;
  width: min(410px, 100%);
  padding: 28px 26px 22px;
  text-align: center;
  border-radius: 18px;
  background: linear-gradient(160deg, #1a0f33, #100722);
  border: 1px solid rgba(217, 70, 239, 0.3);
  box-shadow: 0 26px 70px rgba(3, 0, 10, 0.7);
  animation: ask-in 0.28s cubic-bezier(0.22, 1, 0.36, 1);
}

@keyframes ask-in {
  from { opacity: 0; transform: translateY(12px) scale(0.96); }
  to   { opacity: 1; transform: none; }
}

.ask-glow {
  position: absolute;
  left: 50%;
  top: -120px;
  width: 260px;
  height: 260px;
  margin-left: -130px;
  border-radius: 50%;
  background: radial-gradient(50% 50% at 50% 50%, rgba(217, 70, 239, 0.3), transparent 70%);
  filter: blur(30px);
  pointer-events: none;
}

.ask-icon {
  position: relative;
  width: 52px;
  height: 52px;
  margin: 0 auto 14px;
  display: grid;
  place-items: center;
  border-radius: 14px;
  background: rgba(217, 70, 239, 0.14);
  border: 1px solid rgba(217, 70, 239, 0.35);
}

.ask-icon svg {
  width: 26px;
  height: 26px;
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.ask-sheet h3 {
  position: relative;
  font-size: 19px;
  font-weight: 800;
  margin-bottom: 8px;
}

.ask-text {
  position: relative;
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--muted);
  margin-bottom: 22px;
}

/* Recapitulatif d'une action lourde : paires libelle / valeur en deux colonnes.
   La grille laisse les libelles s'aligner sans largeur fixe, ce qui tient aussi
   bien en francais qu'en anglais ou les mots sont plus courts. */
.ask-recap {
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 9px 16px;
  margin: 0 0 20px;
  padding: 14px 16px;
  text-align: left;
  border: 1px solid var(--border);
  border-radius: 12px;
  /* Fond legerement plus clair que la fenetre : le bloc se detache sans
     introduire de nouvelle couleur dans la charte. */
  background: rgba(124, 58, 237, 0.07);
}
.ask-recap[hidden] { display: none; }

.ask-recap dt {
  font-size: 12px;
  color: var(--muted);
  /* Les libelles tiennent sur une ligne : casser « Compte emetteur » en deux
     desalignerait la valeur en regard. */
  white-space: nowrap;
}

.ask-recap dd {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  /* Un nom de serveur ou un pseudo peut etre long et sans espace : sans cela
     il elargirait la fenetre au lieu de passer a la ligne. */
  overflow-wrap: anywhere;
}

/* Le message cite se lit comme une citation, pas comme une donnee : italique
   et barre laterale le distinguent des autres valeurs du recapitulatif. */
.ask-recap dd.ask-quote {
  font-weight: 500;
  font-style: italic;
  color: var(--v-100);
  padding-left: 10px;
  border-left: 2px solid var(--v-400);
}

/* Champ de confirmation (mot de passe avant une action irreversible). Il n'est
   present que dans ce cas, d'ou la marge portee ici plutot que sur .ask-text. */
.ask-field { position: relative; margin: 0 0 14px; text-align: left; }
.ask-field[hidden] { display: none; }
.ask-field input { width: 100%; }

/* Le bouton grise doit se lire comme inactif : sans cela il garde le rouge vif
   de .danger et invite au clic alors qu'il ne repond pas. */
.ask-actions .btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  box-shadow: none;
}

.ask-actions { position: relative; display: flex; gap: 10px; }
.ask-actions .btn { flex: 1; padding-top: 10px; padding-bottom: 10px; }

@media (max-width: 420px) {
  .ask-actions { flex-direction: column-reverse; }
}

/* -------------------------------------------------------- menu du compte */

/* Ancre sous la pastille, aligne a droite : la barre finit la, le menu ne doit
   pas deborder du cote oppose. */
.user-menu {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  z-index: 60;
  width: 286px;
  padding: 7px;
  border-radius: 16px;
  background: linear-gradient(160deg, rgba(30, 14, 60, 0.98), rgba(10, 4, 19, 0.99));
  border: 1px solid rgba(168, 85, 247, 0.28);
  box-shadow: 0 18px 44px rgba(4, 1, 10, 0.62), 0 0 0 1px rgba(124, 58, 237, 0.1);
  /* Descend en s'ouvrant depuis la pastille dont il est issu, plutot que
     d'apparaitre d'un coup. */
  animation: menu-ouvre 0.18s cubic-bezier(0.3, 1.2, 0.5, 1);
  transform-origin: top right;
  /* Le menu compte assez d'entrees pour depasser un ecran court : il defile
     alors au lieu de sortir sous le bas de la fenetre. */
  max-height: calc(100vh - 96px);
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* hidden ne suffit pas : la regle ci-dessus pose un display implicite via
   position, et l'attribut serait ignore. */
.user-menu[hidden] { display: none; }

@keyframes menu-ouvre {
  from { opacity: 0; transform: translateY(-6px) scale(0.96); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

/* Rappel de l'identite : le menu peut s'ouvrir sur un ecran ou le pseudo est
   masque faute de place. */
.user-menu-head {
  display: flex;
  /* En ligne depuis l'arrivee de la photo : elle se pose a gauche, le nom et
     l'adresse s'empilent a sa droite. */
  align-items: center;
  gap: 11px;
  padding: 10px 12px 12px;
  margin-bottom: 5px;
  border-bottom: 1px solid rgba(168, 85, 247, 0.16);
}

/* min-width: 0 indispensable : sans lui, une adresse longue refuse de se
   tronquer et elargit le menu au lieu de poser ses points de suspension. */
.user-menu-ident {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.menu-avatar {
  flex: none;
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  overflow: hidden;
  background: linear-gradient(135deg, var(--v-400), #c026d3);
  border: 1px solid rgba(168, 85, 247, 0.3);
  font-family: "Outfit", system-ui, sans-serif;
  font-size: 17px;
  font-weight: 900;
  color: #fff;
}

/* Le disque est deja pose par le parent : l'image le remplit sans se
   deformer, quel que soit le rapport d'origine du fichier. */
.menu-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.user-menu-head b {
  font-family: "Outfit", system-ui, sans-serif;
  font-size: 14.5px;
  font-weight: 900;
  color: var(--v-100);
}

.user-menu-head span {
  font-size: 11.5px;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.user-menu-item {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  padding: 9px 11px;
  border: none;
  border-radius: 11px;
  background: none;
  color: var(--v-100);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: background 0.16s ease, color 0.16s ease;
}

/* Le display:flex ci-dessus l'emporte sur l'attribut hidden : sans cette
   regle, l'entree reservee au staff restait visible pour tout le monde. */
.user-menu-item[hidden] { display: none; }

.user-menu-item svg {
  flex: none;
  width: 19px;
  height: 19px;
  color: var(--v-300);
  transition: color 0.16s ease;
}

/* Le libelle et sa precision sont empiles : la seconde ligne explique la
   destination sans qu'il faille l'essayer pour la decouvrir. */
.user-menu-item span {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.user-menu-item b { font-size: 13px; font-weight: 600; }

.user-menu-item i {
  font-size: 11px;
  font-style: normal;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.user-menu-item:hover,
.user-menu-item:focus-visible {
  background: rgba(124, 58, 237, 0.16);
  outline: none;
}

.user-menu-item:hover svg,
.user-menu-item:focus-visible svg { color: var(--v-200); }

.user-menu-sep {
  height: 1px;
  margin: 5px 9px;
  background: rgba(168, 85, 247, 0.16);
}

/* La sortie ne se teinte qu'au survol : rouge en permanence, elle attirerait
   l'oeil plus que les actions courantes. */
.user-menu-item.danger:hover,
.user-menu-item.danger:focus-visible {
  background: rgba(239, 68, 68, 0.14);
  color: #fecaca;
}
.user-menu-item.danger:hover svg,
.user-menu-item.danger:focus-visible svg { color: #fca5a5; }

/* Le menu porte deja la deconnexion : sous cette largeur, le bouton de la
   barre ferait double emploi et vole la place du pseudo. */
@media (max-width: 860px) {
  .user-menu { width: min(286px, calc(100vw - 28px)); }
}

@media (prefers-reduced-motion: reduce) {
  .user-menu { animation: none; }
}

/* ------------------------------------------------------- fiche du compte */

/* Deux colonnes : la consultation a gauche, les operations a droite. La
   colonne de gauche est plus etroite -- elle ne fait que lister, tandis que
   les formulaires ont besoin de largeur pour leurs champs. */
.account-grid {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1fr);
  gap: 18px;
  align-items: start;
}

/* L'attribut hidden pose display:none, mais la regle ci-dessus est plus
   specifique et l'emportait : le panneau « Compte » restait affiche sous les
   autres onglets. Meme cause que .modal-backdrop plus haut. */
.account-grid[hidden] { display: none; }

.account-col { display: flex; flex-direction: column; gap: 18px; }

.account-card { display: flex; flex-direction: column; }

/* ------------------------------------------------------------- portefeuille */

/* Le solde est l'information que l'on vient chercher : il occupe le haut de la
   carte, en gros, avec l'action de rechargement juste a cote. */
.wallet-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}

.wallet-solde { display: flex; flex-direction: column; gap: 2px; }

.wallet-label {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--v-200);
}

.wallet-montant {
  font-size: 30px;
  font-weight: 800;
  line-height: 1.1;
  background: linear-gradient(120deg, #fff, #d8b4fe);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* Etat de l'abonnement. Le liseré gauche porte le niveau d'alerte : neutre
   tant que tout va bien, ambre si le solde ne suffira pas, rouge des la
   premiere tentative echouee. */
/* ------------------------------------------------------------- fidelite */

/* La teinte du rang est posee en variable par le script : une seule regle
   suffit alors pour la medaille, la jauge et le halo. */
.loyalty { --rang: #94a3b8; padding-top: 4px; }

.loyalty-top {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 16px;
}

/* Ecusson du rang : un SVG pose sans fond ni cadre, pour que la silhouette du
   blason reste lisible. Le halo derriere renforce les rangs eleves. */
.loyalty-medal {
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
  flex-shrink: 0;
  filter: drop-shadow(0 3px 10px color-mix(in srgb, var(--rang) 40%, transparent));
  animation: medal-in 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}

.loyalty-medal svg { display: block; overflow: visible; }

/* L'ecusson se pose en arrivant : la recompense se remarque. */
@keyframes medal-in {
  from { transform: scale(0.75) rotate(-8deg); opacity: 0; }
  to { transform: none; opacity: 1; }
}

/* Les deux plus hauts rangs respirent doucement : ce sont les seuls, sinon
   l'effet perd sa valeur de distinction. */
.loyalty-medal.rang-platine,
.loyalty-medal.rang-diamant { animation: medal-in 0.5s cubic-bezier(0.22, 1, 0.36, 1), medal-glow 3.4s ease-in-out 0.5s infinite; }

@keyframes medal-glow {
  0%, 100% { filter: drop-shadow(0 3px 10px color-mix(in srgb, var(--rang) 40%, transparent)); }
  50% { filter: drop-shadow(0 3px 20px color-mix(in srgb, var(--rang) 85%, transparent)); }
}

.loyalty-ident { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

.loyalty-ident b {
  font-family: "Outfit", system-ui, sans-serif;
  font-size: 19px;
  font-weight: 900;
  letter-spacing: -0.02em;
  color: var(--rang);
}

.loyalty-ident span { font-size: 12.5px; color: var(--v-300); }

.loyalty-bar {
  position: relative;
  height: 10px;
  border-radius: 999px;
  overflow: hidden;
  background: rgba(124, 58, 237, 0.16);
  border: 1px solid rgba(168, 85, 247, 0.22);
}

/* La largeur est posee par le script ; la transition fait le reste. Elle est
   longue et amortie pour que la progression se remarque. */
.loyalty-fill {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--rang) 55%, #7c3aed 45%),
    var(--rang));
  box-shadow: 0 0 16px color-mix(in srgb, var(--rang) 55%, transparent);
  transition: width 1.1s cubic-bezier(0.22, 1, 0.36, 1);
}

/* Reflet qui balaie la jauge : signale que le rang est vivant, pas figé. */
.loyalty-fill::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 20%, rgba(255, 255, 255, 0.42) 50%, transparent 80%);
  transform: translateX(-100%);
  animation: loyalty-sweep 2.8s ease-in-out infinite;
}

@keyframes loyalty-sweep {
  0%, 55% { transform: translateX(-100%); }
  100% { transform: translateX(100%); }
}

.loyalty-next {
  margin-top: 10px;
  font-size: 13px;
  color: var(--v-300);
}

.loyalty-steps {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 16px 0 0;
  padding: 0;
  list-style: none;
  counter-reset: rang;
}

.loyalty-step {
  --rang: #94a3b8;
  flex: 1 1 auto;
  padding: 6px 4px;
  border-radius: 8px;
  text-align: center;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--v-400);
  background: rgba(124, 58, 237, 0.07);
  border: 1px solid transparent;
  transition: color 0.2s ease, background 0.2s ease, border-color 0.2s ease;
}

/* Un rang franchi reste colore mais discret ; le rang courant est le seul mis
   en avant, pour qu'on repere sa position d'un regard. */
.loyalty-step.done {
  color: color-mix(in srgb, var(--rang) 75%, #fff 25%);
  background: color-mix(in srgb, var(--rang) 12%, transparent);
}

.loyalty-step.now {
  color: #140826;
  background: var(--rang);
  border-color: color-mix(in srgb, var(--rang) 60%, #fff 40%);
  box-shadow: 0 0 18px color-mix(in srgb, var(--rang) 45%, transparent);
}

/* Sur un ecran etroit, six etiquettes sur une ligne deviennent illisibles :
   on les laisse se repartir sur deux rangees. */
@media (max-width: 520px) {
  .loyalty-step { flex: 1 1 30%; }
}

.loyalty-help {
  margin: 0 0 16px;
  font-size: 13px;
  line-height: 1.6;
  color: var(--v-300);
}

.loyalty-scale { margin: 0; padding: 0; list-style: none; }

.scale-row {
  --rang: #94a3b8;
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 10px 12px;
  border-radius: 10px;
  border: 1px solid transparent;
}

.scale-row + .scale-row { margin-top: 4px; }

.scale-row b {
  flex: 1;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--v-200);
}

.scale-need {
  font-size: 12px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* Recompense du dernier rang : mise en avant, c'est l'argument du programme. */
.scale-gift {
  margin-left: 10px;
  padding: 3px 9px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 800;
  white-space: nowrap;
  color: #d9f99d;
  background: rgba(132, 204, 22, 0.14);
  border: 1px solid rgba(132, 204, 22, 0.32);
}

/* Ecusson miniature. Les rangs pas encore atteints sont ternis plutot que
   caches : on voit ce qui reste a gagner. */
.scale-dot {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  flex-shrink: 0;
  opacity: 0.42;
  filter: grayscale(0.65);
  transition: opacity 0.25s ease, filter 0.25s ease, transform 0.25s ease;
}

.scale-dot svg { display: block; }

.scale-row.done .scale-dot { opacity: 1; filter: none; }

/* Le rang courant est le seul encadre : la ligne se repere sans avoir a lire. */
.scale-row.now {
  background: color-mix(in srgb, var(--rang) 12%, transparent);
  border-color: color-mix(in srgb, var(--rang) 38%, transparent);
}

.scale-row.now .scale-dot {
  opacity: 1;
  filter: none;
  transform: scale(1.16);
}

/* Survol : l'ecusson vise reprend ses couleurs, meme non atteint. Detail
   volontairement incitatif, on regarde ce qui viendra ensuite. */
.scale-row:hover .scale-dot { opacity: 0.9; filter: grayscale(0.15); }

.scale-row.now b { color: color-mix(in srgb, var(--rang) 78%, #fff 22%); }

/* Le balayage est purement decoratif : il disparait si l'utilisateur a demande
   moins d'animations. */
@media (prefers-reduced-motion: reduce) {
  .loyalty-fill::after { animation: none; }
  .loyalty-fill { transition: none; }
  .loyalty-medal,
  .loyalty-medal.rang-platine,
  .loyalty-medal.rang-diamant { animation: none; }
}

/* ------------------------------------------------------------ commandes */

/* Abonnement en cours : trois lignes clef/valeur, la valeur alignee a droite
   pour que les montants se comparent d'un coup d'oeil. */
.abo-line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding: 10px 0;
  border-bottom: 1px solid rgba(168, 85, 247, 0.12);
}

.abo-line:last-child { border-bottom: 0; }

.abo-line span {
  font-size: 13.5px;
  color: var(--v-300);
}

.abo-line b {
  font-family: "Outfit", system-ui, sans-serif;
  font-size: 15px;
  font-weight: 800;
  color: var(--v-100);
}

.orders-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.order-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 13px 15px;
  border-radius: 12px;
  background: rgba(124, 58, 237, 0.07);
  border: 1px solid rgba(168, 85, 247, 0.16);
  transition: background 0.18s ease, border-color 0.18s ease;
}

.order-row:hover {
  background: rgba(124, 58, 237, 0.12);
  border-color: rgba(168, 85, 247, 0.3);
}

.order-main {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

.order-main b {
  font-family: "Outfit", system-ui, sans-serif;
  font-size: 14.5px;
  font-weight: 800;
  color: var(--v-100);
}

/* La reference se lit caractere par caractere quand on la dicte au support :
   une chasse fixe evite de confondre 0 et O. */
.order-main span {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 11.5px;
  color: var(--v-400);
  word-break: break-all;
}

.order-side {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-shrink: 0;
}

.order-price {
  font-family: "Outfit", system-ui, sans-serif;
  font-size: 15px;
  font-weight: 900;
  color: var(--v-100);
}

.order-badge {
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 11.5px;
  font-weight: 700;
  white-space: nowrap;
  color: var(--v-200);
  background: rgba(124, 58, 237, 0.16);
  border: 1px solid rgba(168, 85, 247, 0.3);
}

.order-badge.ok {
  color: #86efac;
  background: rgba(34, 197, 94, 0.12);
  border-color: rgba(34, 197, 94, 0.35);
}

.order-badge.wait {
  color: #fde68a;
  background: rgba(251, 191, 36, 0.12);
  border-color: rgba(251, 191, 36, 0.35);
}

.order-badge.bad {
  color: #fca5a5;
  background: rgba(239, 68, 68, 0.12);
  border-color: rgba(239, 68, 68, 0.35);
}

.order-badge.off {
  color: var(--v-400);
  background: rgba(148, 163, 184, 0.1);
  border-color: rgba(148, 163, 184, 0.25);
}

/* La note occupe toute la largeur sous la ligne : elle commente l'achat sans
   s'inserer entre le titre et le montant. */
.order-note {
  flex-basis: 100%;
  font-size: 12px;
  font-style: normal;
  color: var(--v-300);
}

.orders-empty {
  padding: 18px 4px;
  font-size: 13.5px;
  color: var(--v-400);
}

@media (max-width: 560px) {
  .order-row { align-items: flex-start; }
  .order-side { width: 100%; justify-content: space-between; }
}

.wallet-abo {
  margin-top: 14px;
  padding: 11px 14px;
  border-radius: 10px;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--v-200);
  background: rgba(124, 58, 237, 0.1);
  border-left: 3px solid rgba(168, 85, 247, 0.5);
}

.wallet-abo[hidden] { display: none; }

.wallet-abo.warn {
  color: #fde68a;
  background: rgba(251, 191, 36, 0.1);
  border-left-color: rgba(251, 191, 36, 0.6);
}

.wallet-abo.danger {
  color: #fecdd3;
  background: rgba(251, 113, 133, 0.12);
  border-left-color: rgba(251, 113, 133, 0.65);
}

.wallet-form { margin-top: 18px; }
.wallet-form[hidden] { display: none; }

/* Le symbole euro est pose dans le champ plutot qu'a cote : colle a la valeur,
   il se lit comme une unite et non comme une etiquette separee. */
.wallet-amount-row { position: relative; }
.wallet-amount-row input { width: 100%; padding-right: 34px; }

.wallet-currency {
  position: absolute;
  right: 14px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--v-200);
  font-weight: 700;
  pointer-events: none;
}

.wallet-presets { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }

.wallet-presets .btn.active,
.wallet-method.active,
.wallet-option.active {
  background: rgba(124, 58, 237, 0.3);
  border-color: rgba(168, 85, 247, 0.55);
  color: #fff;
}

.wallet-methods { display: flex; flex-wrap: wrap; gap: 8px; }

.wallet-method,
.wallet-option {
  padding: 9px 15px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 600;
  color: var(--v-200);
  background: rgba(124, 58, 237, 0.12);
  border: 1px solid rgba(168, 85, 247, 0.22);
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}

.wallet-method:hover,
.wallet-option:hover { background: rgba(124, 58, 237, 0.22); color: #fff; }

/* Le reseau est un sous-choix du moyen : plus petit et moins contraste, pour
   qu'on ne le confonde pas avec un moyen de paiement a part entiere. */
.wallet-option { font-size: 12px; padding: 7px 12px; opacity: 0.9; }

.wallet-table { width: 100%; border-collapse: collapse; }

.wallet-table td {
  padding: 11px 0;
  border-bottom: 1px solid rgba(168, 85, 247, 0.12);
  font-size: 13.5px;
}

.wallet-table tr:last-child td { border-bottom: 0; }

.wallet-date { color: var(--v-200); white-space: nowrap; width: 1%; padding-right: 16px; }

/* Le montant ferme la ligne, aligne a droite : les chiffres se comparent d'une
   ligne a l'autre sans avoir a les chercher. */
.wallet-delta { text-align: right; font-weight: 700; white-space: nowrap; }
.wallet-delta.in { color: #86efac; }
.wallet-delta.out { color: #fda4af; }

/* Zone dangereuse. La bordure rouge et le titre assorti la detachent du reste
   de la colonne : on doit voir qu'on change de registre avant de lire. */
.account-danger {
  border-color: rgba(251, 113, 133, 0.28);
  background:
    linear-gradient(180deg, rgba(251, 113, 133, 0.07), rgba(251, 113, 133, 0)) ,
    var(--panel, rgba(20, 12, 34, 0.6));
}

.account-danger .panel-head h3 { color: #fda4af; }

/* Le bouton reste a sa largeur naturelle et se range a droite : pleine largeur,
   il aurait le poids d'une action principale. */
.account-danger .account-actions { justify-content: flex-end; }

/* Sous cette largeur les deux colonnes se superposent : cote a cote, les
   champs deviendraient trop etroits pour une adresse e-mail. */
@media (max-width: 900px) {
  .account-grid { grid-template-columns: minmax(0, 1fr); }
}

.account-form .panel-head { margin-bottom: 4px; }

/* Explique ce que fait le formulaire avant qu'on le remplisse : ces deux
   operations ont des consequences qu'on ne devine pas depuis les champs. */
.account-note {
  margin: 0 0 16px;
  color: var(--muted);
  font-size: 12.5px;
  line-height: 1.55;
}

.account-note.strong {
  padding: 11px 13px;
  border-radius: 11px;
  background: rgba(124, 58, 237, 0.12);
  border: 1px solid rgba(168, 85, 247, 0.22);
  color: var(--v-100);
}

.account-note.strong b { color: #fff; word-break: break-all; }

/* Message qui remplace un formulaire indisponible (compte Discord/Google). */
.account-locked {
  margin: 0;
  padding: 13px 14px;
  border-radius: 12px;
  background: rgba(124, 58, 237, 0.08);
  border: 1px dashed rgba(168, 85, 247, 0.28);
  color: var(--muted);
  font-size: 12.5px;
  line-height: 1.55;
}

.account-actions { display: flex; gap: 10px; }
.account-actions .btn { flex: 1; }

.btn.block { width: 100%; }

/* Le code se lit chiffre par chiffre : espacement large et police a chasse
   fixe, comme dans l'e-mail qui le transporte. */
.code-input {
  font-family: "JetBrains Mono", "Courier New", monospace;
  font-size: 22px;
  font-weight: 700;
  letter-spacing: 8px;
  text-align: center;
}

/* Champ de mot de passe avec son oeil : meme motif que la page de connexion. */
.pass-wrap { position: relative; }

.pass-wrap input { width: 100%; padding-right: 44px; }

.pass-eye {
  position: absolute;
  top: 50%;
  right: 6px;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: none;
  border-radius: 9px;
  background: none;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.16s ease, background 0.16s ease;
}

.pass-eye:hover { color: var(--v-200); background: rgba(124, 58, 237, 0.14); }
.pass-eye svg { width: 18px; height: 18px; }

/* Champ verrouille : il porte une valeur mais ne se modifie pas. Le fond en
   retrait et le curseur par defaut le disent avant qu'on essaie de taper. */
.pass-wrap input[readonly] {
  background: rgba(124, 58, 237, 0.07);
  border-color: rgba(168, 85, 247, 0.16);
  color: var(--muted);
  cursor: default;
}

/* Le stylo est la seule chose vivante de la ligne au repos : il s'anime au
   survol pour signaler que c'est par la que l'on passe. */
#email-edit { color: var(--v-300); }
#email-edit:hover { color: var(--v-100); background: rgba(124, 58, 237, 0.18); }

/* Une fois le champ ouvert, le stylo disparait : il n'a plus rien a
   deverrouiller, et l'annulation se fait par le bouton dedie. */
.pass-wrap input:not([readonly]) + #email-edit { display: none; }

/* Bandeau d'identite : l'initiale tient lieu d'avatar, aucun envoi d'image
   n'etant prevu. */
.account-head {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px;
  margin-bottom: 20px;
  border-radius: 14px;
  background: linear-gradient(135deg, rgba(76, 29, 149, 0.32), rgba(10, 4, 19, 0.5));
  border: 1px solid var(--border);
}

.account-avatar {
  flex: none;
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--v-400), #c026d3);
  box-shadow: 0 4px 16px rgba(124, 58, 237, 0.4);
  font-family: "Outfit", system-ui, sans-serif;
  font-size: 22px;
  font-weight: 900;
  color: #fff;
  text-transform: uppercase;
  /* Devenu un bouton : on efface la parure native sans toucher au reste. */
  padding: 0;
  border: none;
  overflow: hidden;
  cursor: pointer;
  transition: box-shadow 0.2s ease, transform 0.2s ease;
}

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

/* -------- changement de la photo de profil -------- */

/* Conteneur en position relative : le stylo se pose sur le bord du disque,
   comme la pastille d'appareil photo de Discord. */
.avatar-edit {
  position: relative;
  flex: none;
  line-height: 0;
}

.avatar-pen {
  position: absolute;
  right: -2px;
  bottom: -2px;
  display: grid;
  place-items: center;
  width: 21px;
  height: 21px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--v-400), #c026d3);
  /* Le liensre du fond de la carte detache la pastille du disque, sinon les
     deux cercles se touchent et la forme devient illisible. */
  border: 2px solid #150a28;
  color: #fff;
  /* Le clic passe au disque : deux cibles distinctes pour une meme action
     compliqueraient la navigation au clavier sans rien apporter. */
  pointer-events: none;
  transition: transform 0.2s ease;
}

.avatar-pen svg { width: 11px; height: 11px; }

.avatar-edit:hover .account-avatar {
  box-shadow: 0 6px 22px rgba(124, 58, 237, 0.55);
  transform: scale(1.03);
}

.avatar-edit:hover .avatar-pen { transform: scale(1.12); }

.account-avatar:focus-visible {
  outline: 2px solid var(--v-300);
  outline-offset: 3px;
}

/* Pendant l'envoi : le disque s'attenue et pulse, un retour suffisant pour
   une operation qui dure moins d'une seconde. */
.account-avatar.is-busy {
  opacity: 0.55;
  animation: avatar-busy 1s ease-in-out infinite;
  pointer-events: none;
}

@keyframes avatar-busy {
  0%, 100% { opacity: 0.55; }
  50%      { opacity: 0.85; }
}

/* -------- recadrage de la photo -------- */

.crop-sheet {
  width: min(400px, 92vw);
  text-align: center;
}

.crop-hint {
  margin: 6px 0 16px;
  font-size: 12.5px;
  color: var(--muted);
}

/* Scene carree : aspect-ratio plutot qu'une hauteur fixe, pour qu'elle suive
   la largeur disponible sans jamais devenir rectangulaire -- le masque rond
   deviendrait sinon une ellipse. */
.crop-stage {
  position: relative;
  width: 100%;
  aspect-ratio: 1;
  /* Plafond en hauteur de fenetre : sur un ecran bas ou en paysage sur
     mobile, une scene calee sur la seule largeur poussait le curseur de zoom
     et les boutons hors de vue. La largeur suit alors, le carre est preserve
     par aspect-ratio. */
  max-height: 46vh;
  max-width: 46vh;
  margin: 0 auto;
  border-radius: 14px;
  overflow: hidden;
  background: #0a0413;
  cursor: grab;
  /* Coupe le defilement de la page et les gestes de zoom du navigateur : le
     glissement doit deplacer la photo, pas emporter la vue. */
  touch-action: none;
  user-select: none;
}

.crop-stage.is-dragging { cursor: grabbing; }

.crop-stage:focus-visible {
  outline: 2px solid var(--v-300);
  outline-offset: 3px;
}

/* L'image est centree par translate(-50%, -50%) ; le script ajoute ensuite son
   propre decalage. max-width: none neutralise la regle globale sur img, qui
   l'empecherait de depasser la scene -- or c'est precisement ce debord qui
   permet de cadrer. */
.crop-stage img {
  position: absolute;
  top: 50%;
  left: 50%;
  max-width: none;
  transform-origin: center;
  /* Une image glissee au pointeur ne doit pas declencher le glisser-deposer
     natif du navigateur, qui detournerait le geste. */
  -webkit-user-drag: none;
  pointer-events: none;
}

/* Le trou rond est creuse par une ombre portee interieure demesuree : tout ce
   qui est hors du disque est assombri, le disque reste net. Plus simple qu'un
   masque, et le contour se dessine dans la foulee.

   Le voile laisse volontairement voir les bords : on choisit mieux son cadre
   en gardant sous les yeux ce que l'on ecarte. */
.crop-mask {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  box-shadow: 0 0 0 9999px rgba(8, 3, 16, 0.72);
  /* Le liseré marque la limite exacte de la decoupe. */
  border: 2px solid rgba(216, 180, 254, 0.55);
  /* Le masque ne doit pas intercepter le glissement destine a la scene. */
  pointer-events: none;
}

.crop-zoom {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 18px 2px 4px;
}

.crop-zoom-icon {
  flex: none;
  display: grid;
  place-items: center;
  color: var(--v-300);
}

.crop-zoom-icon svg { width: 18px; height: 18px; }
.crop-zoom-icon.small svg { width: 14px; height: 14px; }

/* Curseur redessine : la parure native varie trop d'un navigateur a l'autre
   pour tenir dans le theme. Les deux familles de selecteurs (WebKit et
   Firefox) ne peuvent pas etre groupees -- un selecteur inconnu invaliderait
   toute la regle pour l'autre moteur. */
.crop-zoom input[type="range"] {
  flex: 1;
  -webkit-appearance: none;
  appearance: none;
  height: 5px;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--v-400), #c026d3);
  cursor: pointer;
}

.crop-zoom input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 17px;
  height: 17px;
  border-radius: 50%;
  background: #fff;
  border: 2px solid var(--v-400);
  box-shadow: 0 2px 8px rgba(124, 58, 237, 0.5);
  cursor: grab;
}

.crop-zoom input[type="range"]::-moz-range-thumb {
  width: 17px;
  height: 17px;
  border-radius: 50%;
  background: #fff;
  border: 2px solid var(--v-400);
  box-shadow: 0 2px 8px rgba(124, 58, 237, 0.5);
  cursor: grab;
  /* Firefox pose une bordure par defaut sur la piste, invisible ailleurs. */
  border-color: var(--v-400);
}

.crop-zoom input[type="range"]:focus-visible {
  outline: 2px solid var(--v-300);
  outline-offset: 4px;
}

.avatar-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 5px;
}

/* Liens plutot que boutons : ces actions accompagnent la photo, elles ne
   doivent pas peser autant que « Changer de formule » juste en dessous. */
.avatar-link {
  padding: 0;
  border: none;
  background: none;
  color: var(--v-300);
  font-family: inherit;
  font-size: 11.5px;
  font-weight: 600;
  cursor: pointer;
  transition: color 0.18s ease;
}

.avatar-link:hover { color: var(--v-100); text-decoration: underline; text-underline-offset: 3px; }
.avatar-link.danger { color: #fca5a5; }
.avatar-link.danger:hover { color: #fecaca; }

/* display:flex l'emporterait sur hidden : sans cette regle, « Retirer »
   resterait visible pour un compte sans photo. */
.avatar-link[hidden] { display: none; }

@media (prefers-reduced-motion: reduce) {
  .account-avatar,
  .avatar-pen,
  .chip-avatar { transition: none; }
  .account-avatar.is-busy { animation: none; }
  .avatar-edit:hover .account-avatar,
  .avatar-edit:hover .avatar-pen,
  .user-chip .chip-name:hover .chip-avatar { transform: none; }
}

.account-ident { display: flex; flex-direction: column; gap: 3px; min-width: 0; }

.account-ident b {
  font-family: "Outfit", system-ui, sans-serif;
  font-size: 17px;
  font-weight: 900;
  color: var(--v-100);
}

/* L'adresse peut etre longue : elle se tronque plutot que d'elargir la fiche. */
.account-ident > span:not(.account-role) {
  font-size: 12.5px;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Reprend la parure des etiquettes de rang de la barre, en un cran plus grand
   puisque la fiche laisse de la place. */
.account-role {
  align-self: flex-start;
  margin-top: 4px;
  padding: 4px 11px;
  border-radius: 999px;
  border: 1px solid var(--tag-edge, rgba(148, 163, 184, 0.5));
  background: var(--tag-tint, rgba(148, 163, 184, 0.12));
  color: var(--tag-ink, #e8eef7);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.account-section {
  margin: 0 0 8px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--v-300);
}

.account-lines { display: flex; flex-direction: column; margin-bottom: 20px; }

.account-line {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 10px 0;
  font-size: 13.5px;
  border-bottom: 1px solid rgba(168, 85, 247, 0.14);
}

.account-line span { color: var(--muted); }
.account-line b { color: var(--v-100); text-align: right; }
.account-line b.oui { color: var(--ok); }
.account-line b.non { color: var(--muted); font-weight: 500; }

/* Colle au bas de la carte quelle que soit la hauteur du contenu, pour que les
   deux colonnes s'alignent. */
.account-foot {
  display: flex;
  gap: 10px;
  margin-top: auto;
  padding-top: 18px;
}
.account-foot .btn { flex: 1; }

/* Sous cette largeur, les deux actions s'empilent plutot que de reduire leurs
   libelles au point de les tronquer. */
@media (max-width: 460px) {
  .account-foot { flex-direction: column; }
  .account-actions { flex-direction: column; }
  .account-head { flex-direction: column; text-align: center; }
  .account-role { align-self: center; }
  /* L'espacement large deborderait sur un petit ecran. */
  .code-input { font-size: 19px; letter-spacing: 5px; }
}

.buy-lines { display: flex; flex-direction: column; margin-bottom: 18px; }

.buy-line {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 11px 0;
  font-size: 13.5px;
  border-bottom: 1px solid rgba(168, 85, 247, 0.14);
}

.buy-line span { color: var(--muted); }
.buy-line b { color: var(--v-100); text-align: right; }
.buy-line.discount b { color: var(--ok); }

.buy-line.total {
  border-bottom: none;
  padding-top: 15px;
  font-size: 15px;
}

.buy-line.total b {
  font-size: 30px;
  font-weight: 900;
  letter-spacing: -0.03em;
  background: linear-gradient(96deg, var(--v-100), var(--accent));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.buy-note {
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--muted);
  text-align: center;
  margin-bottom: 16px;
}

.buy-confirm { width: 100%; }

/* Mise au panier : volontairement secondaire face au bouton de paiement, qui
   reste l'action principale. Meme largeur pour aligner les deux, mais sans le
   degrade plein qui signale l'achat. */
.buy-cart {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  margin-bottom: 10px;
}

.buy-cart[hidden] { display: none; }

.buy-cart-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}

.buy-cart-icon svg { width: 100%; height: 100%; display: block; }

/* Deja au panier : le bouton passe en teinte pleine pour que l'etat se lise
   sans avoir a ouvrir le panier. */
.buy-cart.in-cart {
  border-color: rgba(168, 85, 247, 0.45);
  background: rgba(124, 58, 237, 0.2);
  color: var(--v-100);
}

/* -------------------------------------------------- moyens de paiement */

/* La modale de paiement porte des adresses crypto longues : elle a besoin de
   plus de largeur que le simple recapitulatif d'achat. */
.modal.wide { max-width: 540px; }

.buy-step { margin-bottom: 18px; }

.buy-step-title {
  font-size: 11px;
  font-weight: 900;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 12px;
}

.pay-methods { display: flex; flex-direction: column; gap: 9px; }

.pay-method {
  position: relative;
  overflow: hidden;          /* contient le halo et le liseré animés */
  display: flex;
  flex-direction: column;
  gap: 5px;
  width: 100%;
  padding: 14px 16px;
  border-radius: 14px;
  text-align: left;
  cursor: pointer;
  background: linear-gradient(135deg, rgba(27, 15, 56, 0.6), rgba(10, 4, 19, 0.55));
  border: 1px solid var(--border);
  color: var(--v-100);
  transition: border-color 0.22s ease, background 0.22s ease,
              transform 0.22s ease, box-shadow 0.22s ease;
}

/* Halo qui suit l'entree de la souris. Pose en ::before pour laisser ::after
   au liseré de la carte selectionnee. */
.pay-method::before {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  background: radial-gradient(420px circle at var(--mx, 50%) var(--my, 0%),
              rgba(168, 85, 247, 0.22), transparent 60%);
  opacity: 0;
  transition: opacity 0.25s ease;
  pointer-events: none;      /* ne doit pas intercepter le clic du bouton */
}

.pay-method:hover::before { opacity: 1; }

.pay-method:hover {
  border-color: var(--v-400);
  transform: translateY(-2px);
  box-shadow: 0 10px 26px rgba(124, 58, 237, 0.24);
}

.pay-method.active {
  border-color: var(--accent);
  background: linear-gradient(135deg, rgba(124, 58, 237, 0.28), rgba(217, 70, 239, 0.14));
  box-shadow: 0 0 0 1px var(--accent) inset, 0 8px 28px rgba(217, 70, 239, 0.28);
}

/* Liseré lumineux qui balaie la carte retenue : confirme le choix sans
   ajouter de texte. La bande est large et lente pour rester discrete. */
.pay-method.active::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(100deg, transparent 35%,
              rgba(233, 213, 255, 0.16) 50%, transparent 65%);
  background-size: 300% 100%;
  animation: pay-sweep 3.2s ease-in-out infinite;
  pointer-events: none;
}

@keyframes pay-sweep {
  0%        { background-position: 130% 0; }
  60%, 100% { background-position: -30% 0; }
}

.pay-method-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  font-size: 14px;
}

/* Logo + libelle solidaires a gauche, la pastille d'etat restant a droite. */
.pay-method-name {
  display: flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
}

.pay-method-logo {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  display: block;
  /* Le halo suit la selection : discret au repos, il s'affirme sur la carte
     active sans jamais alterer les couleurs officielles des marques. */
  filter: drop-shadow(0 0 4px rgba(124, 58, 237, 0.4));
  transition: filter 0.22s ease, transform 0.22s ease;
}

.pay-method-logo svg { width: 100%; height: 100%; display: block; border-radius: 5px; }

.pay-method:hover .pay-method-logo { transform: scale(1.06); }

.pay-method.active .pay-method-logo {
  filter: drop-shadow(0 0 7px rgba(217, 70, 239, 0.65));
}

@media (prefers-reduced-motion: reduce) {
  .pay-method:hover .pay-method-logo { transform: none; }
}

.pay-method-desc { font-size: 12px; color: var(--muted); line-height: 1.5; }

.pay-badge {
  padding: 3px 9px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.pay-badge.instant {
  background: rgba(74, 222, 128, 0.16);
  color: var(--ok);
  border: 1px solid rgba(74, 222, 128, 0.32);
}

/* "verification" reste ambre : c'est un code couleur d'attente, pas un element
   de theme. Le violet le confondrait avec les elements selectionnables. */
.pay-badge.manual {
  background: rgba(251, 191, 36, 0.16);
  color: var(--warn);
  border: 1px solid rgba(251, 191, 36, 0.3);
}

/* Le point clignote lentement pour signaler une attente cote humain. */
.pay-badge.manual::before {
  content: "";
  display: inline-block;
  width: 5px; height: 5px;
  margin-right: 6px;
  border-radius: 50%;
  background: var(--warn);
  vertical-align: middle;
  animation: badge-pulse 2s ease-in-out infinite;
}

@keyframes badge-pulse {
  0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(251, 191, 36, 0.5); }
  50%      { opacity: 0.55; box-shadow: 0 0 0 4px rgba(251, 191, 36, 0); }
}

/* Les pastilles ne portent plus que le logo : centrees, elles forment une
   rangee compacte sous la carte du moyen de paiement. */
.pay-options {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 12px;
  justify-content: center;
}

.pay-option {
  position: relative;
  display: grid;
  place-items: center;
  /* Carre : sans texte, un rectangle laisserait du vide autour du logo. */
  width: 52px;
  height: 52px;
  padding: 0;
  border-radius: 14px;
  cursor: pointer;
  background: rgba(10, 4, 19, 0.5);
  border: 1px solid var(--border);
  color: var(--v-100);
  transition: border-color 0.2s ease, background 0.2s ease,
              transform 0.2s ease, box-shadow 0.2s ease;
  /* Apparition en cascade : le retard est pose par --i depuis le JS. backwards
     retient l'etat initial avant le demarrage, sans quoi les pastilles
     clignoteraient a pleine opacite pendant leur delai d'attente. */
  animation: option-in 0.34s ease backwards;
  animation-delay: calc(var(--i, 0) * 55ms);
}

@keyframes option-in {
  from { opacity: 0; transform: translateY(7px) scale(0.97); }
  to   { opacity: 1; transform: none; }
}

/* Le logo est legerement desature au repos : seul le reseau retenu s'affiche
   en pleine couleur, ce qui rend la selection lisible sans texte. */
.pay-option-logo {
  display: block;
  width: 30px;
  height: 30px;
  opacity: 0.72;
  filter: saturate(0.75);
  transition: opacity 0.2s ease, filter 0.2s ease, transform 0.2s ease;
}

.pay-option-logo svg { display: block; width: 100%; height: 100%; }

.pay-option:hover .pay-option-logo,
.pay-option.active .pay-option-logo {
  opacity: 1;
  filter: none;
}

.pay-option.active .pay-option-logo { transform: scale(1.06); }

.pay-option:hover {
  border-color: var(--v-400);
  transform: translateY(-2px);
  box-shadow: 0 7px 18px rgba(124, 58, 237, 0.22);
}

.pay-option.active {
  border-color: var(--accent);
  background: linear-gradient(135deg, rgba(124, 58, 237, 0.3), rgba(217, 70, 239, 0.16));
  box-shadow: 0 0 0 1px rgba(217, 70, 239, 0.5) inset,
              0 8px 22px rgba(217, 70, 239, 0.26);
}

/* Pastille de confirmation sur le reseau retenu : plus lisible qu'une simple
   difference de teinte, notamment pour un daltonien. */
.pay-option.active::after {
  content: "✓";
  position: absolute;
  top: -6px; right: -6px;
  width: 18px; height: 18px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--v-400), var(--accent));
  color: #fff;
  font-size: 10px;
  font-weight: 900;
  box-shadow: 0 2px 8px rgba(217, 70, 239, 0.55);
  animation: option-check 0.3s ease backwards;
}

@keyframes option-check {
  from { opacity: 0; transform: scale(0.4); }
  to   { opacity: 1; transform: scale(1); }
}

.pay-empty { font-size: 13px; color: var(--muted); text-align: center; padding: 14px; }

/* ------------------------------------------------- ecran de paiement */

/* La fenetre de paiement est la page la plus dense de l'application : elle
   assume sa hauteur plutot que de comprimer l'information. Sur grand ecran
   elle se scinde en deux colonnes, marche a suivre a gauche, QR et suivi a
   droite. */

.pay-backdrop { padding: 24px 16px; overflow-y: auto; }

.pay-sheet {
  position: relative;
  overflow: hidden;
  width: min(920px, 100%);
  max-width: 920px;
  padding: 0;                /* les sections gerent leur propre marge */
  margin: auto;
  border-radius: 22px;
  background: linear-gradient(160deg, #1a0f33 0%, #120825 46%, #0c0519 100%);
  border: 1px solid rgba(168, 85, 247, 0.28);
  box-shadow: 0 30px 90px rgba(3, 0, 10, 0.75),
              0 0 0 1px rgba(217, 70, 239, 0.08) inset;
  animation: pay-sheet-in 0.42s cubic-bezier(0.22, 1, 0.36, 1) backwards;
}

@keyframes pay-sheet-in {
  from { opacity: 0; transform: translateY(18px) scale(0.985); }
  to   { opacity: 1; transform: none; }
}

/* -------- bandeau -------- */

.pay-hero {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 20px;
  padding: 26px 30px 24px;
  border-bottom: 1px solid rgba(168, 85, 247, 0.18);
  background:
    radial-gradient(120% 160% at 0% 0%, rgba(124, 58, 237, 0.32), transparent 60%),
    radial-gradient(90% 140% at 100% 0%, rgba(217, 70, 239, 0.22), transparent 62%);
}

/* Halo lent en arriere-plan : donne de la profondeur sans distraire, car il
   reste tres peu contraste. */
.pay-hero-glow {
  position: absolute;
  inset: -60% -20% auto;
  height: 260px;
  background: radial-gradient(50% 50% at 50% 50%, rgba(217, 70, 239, 0.35), transparent 70%);
  filter: blur(38px);
  animation: pay-glow 9s ease-in-out infinite;
  pointer-events: none;
}

@keyframes pay-glow {
  0%, 100% { transform: translate(-8%, 0) scale(1); opacity: 0.75; }
  50%      { transform: translate(12%, 8%) scale(1.15); opacity: 1; }
}

.pay-hero-main { position: relative; z-index: 1; min-width: 0; }
.pay-hero-id { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }

.pay-chip {
  padding: 5px 11px;
  border-radius: 999px;
  font-family: var(--mono, "JetBrains Mono", monospace);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--v-100);
  background: rgba(124, 58, 237, 0.28);
  border: 1px solid rgba(217, 70, 239, 0.42);
}

.pay-chip.ghost {
  background: rgba(10, 4, 19, 0.45);
  border-color: var(--border);
  color: var(--muted);
  letter-spacing: 0.03em;
}

.pay-hero h3 {
  font-size: 27px;
  font-weight: 800;
  letter-spacing: -0.025em;
  line-height: 1.15;
  margin-bottom: 6px;
}

.pay-hero-sub { font-size: 13px; color: var(--muted); }

.pay-hero-amount {
  position: relative;
  z-index: 1;
  text-align: right;
  flex: 0 0 auto;
}

/* Le montant crypto prime sur le prix en euros : c'est la valeur que le
   client doit reproduire au chiffre pres. */
.pay-crypto-amount {
  display: flex;
  align-items: baseline;
  justify-content: flex-end;
  gap: 7px;
  margin-bottom: 2px;
}

.pay-crypto-figure {
  font-family: var(--mono, "JetBrains Mono", monospace);
  font-size: 25px;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: #fff;
  text-shadow: 0 0 22px rgba(217, 70, 239, 0.55);
}

.pay-crypto-unit {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--v-200);
}

.pay-eur { display: flex; align-items: baseline; justify-content: flex-end; gap: 7px; }

.pay-eur b {
  font-size: 17px;
  font-weight: 800;
  background: linear-gradient(96deg, var(--v-100), var(--accent));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.pay-eur span { font-size: 11.5px; color: var(--muted); }

/* -------- corps en deux colonnes -------- */

.pay-body {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  gap: 26px;
  padding: 24px 30px 8px;
}

/* min-width:0 est indispensable : sans lui une adresse crypto sans espace
   force la colonne a s'elargir et fait deborder la grille. */
.pay-col, .pay-col-main { min-width: 0; }

/* La colonne laterale reste groupee en haut : QR puis suivi, sans etirement. */
.pay-col-side { min-width: 0; display: flex; flex-direction: column; }

.pay-section-title {
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--v-300);
  margin-bottom: 12px;
}

/* Etapes numerotees : le compteur CSS remplace la puce native, ce qui permet
   de styler le numero comme une pastille. */
.pay-steps {
  list-style: none;
  counter-reset: pas;
  margin: 0 0 22px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 11px;
}

.pay-steps li {
  counter-increment: pas;
  position: relative;
  padding-left: 38px;
  font-size: 13.2px;
  line-height: 1.6;
  color: var(--v-100);
  animation: pay-step-in 0.4s ease backwards;
  animation-delay: calc(var(--i, 0) * 70ms);
}

.pay-steps li::before {
  content: counter(pas);
  position: absolute;
  left: 0;
  top: -1px;
  width: 25px;
  height: 25px;
  display: grid;
  place-items: center;
  border-radius: 9px;
  font-size: 11.5px;
  font-weight: 800;
  color: #fff;
  background: linear-gradient(140deg, var(--v-400), var(--accent));
  box-shadow: 0 4px 14px rgba(124, 58, 237, 0.45);
}

@keyframes pay-step-in {
  from { opacity: 0; transform: translateX(-8px); }
  to   { opacity: 1; transform: none; }
}

/* Le montant a recopier est mis en avant dans le texte de l'etape. */
.pay-steps b {
  font-family: var(--mono, "JetBrains Mono", monospace);
  color: #fff;
  background: rgba(217, 70, 239, 0.18);
  padding: 1px 6px;
  border-radius: 6px;
  border: 1px solid rgba(217, 70, 239, 0.3);
}

.pay-fields { display: flex; flex-direction: column; gap: 10px; margin-bottom: 4px; }

/* Chaque donnee a copier occupe sa propre carte : libelle au-dessus, valeur
   en pleine largeur en dessous. Une adresse crypto est trop longue pour tenir
   sur la meme ligne que son intitule sans devenir illisible. */
.pay-field {
  position: relative;
  overflow: hidden;
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas: "label bouton" "valeur bouton";
  align-items: center;
  gap: 4px 12px;
  padding: 12px 14px;
  border-radius: 13px;
  background: linear-gradient(140deg, rgba(27, 15, 56, 0.72), rgba(10, 4, 19, 0.62));
  border: 1px solid var(--border);
  transition: border-color 0.22s ease, box-shadow 0.22s ease, transform 0.22s ease;
}

.pay-field:hover {
  border-color: var(--v-400);
  box-shadow: 0 8px 24px rgba(124, 58, 237, 0.2);
}

/* Confirmation visuelle apres copie : un bref eclat traverse la carte. */
.pay-field.copied {
  border-color: var(--ok);
  box-shadow: 0 0 0 1px rgba(74, 222, 128, 0.35) inset,
              0 8px 26px rgba(74, 222, 128, 0.22);
}

.pay-field.copied::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 20%, rgba(74, 222, 128, 0.22) 50%, transparent 80%);
  animation: pay-copied 0.7s ease;
  pointer-events: none;
}

@keyframes pay-copied {
  from { transform: translateX(-100%); }
  to   { transform: translateX(100%); }
}

.pay-field-label {
  grid-area: label;
  font-size: 10px;
  font-weight: 700;
  color: var(--v-300);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

/* Une adresse crypto ne doit jamais deborder ni etre tronquee : elle doit
   rester lisible et selectionnable en entier. */
.pay-field-value {
  grid-area: valeur;
  min-width: 0;
  font-family: var(--mono, "JetBrains Mono", monospace);
  font-size: 12.5px;
  line-height: 1.5;
  color: #fff;
  word-break: break-all;
  user-select: all;
}

/* Bouton de copie reduit a son symbole : carre, il libere la largeur au
   profit de la valeur, souvent longue (adresse de portefeuille). */
.pay-copy {
  grid-area: bouton;
  align-self: center;
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
  display: grid;
  place-items: center;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: rgba(124, 58, 237, 0.16);
  color: var(--v-200);
  cursor: pointer;
  transition: color 0.18s, background 0.18s, border-color 0.18s, transform 0.18s;
}

.pay-copy svg { width: 17px; height: 17px; display: block; }

.pay-copy:hover {
  color: #fff;
  background: rgba(124, 58, 237, 0.34);
  border-color: var(--v-300);
  transform: translateY(-1px);
}

.pay-copy:active { transform: scale(0.92); }

/* Etat de reussite : la coche verte reprend la couleur de l'eclat qui
   traverse la carte, les deux signaux se repondent. */
.pay-copy.done {
  color: var(--ok);
  background: rgba(74, 222, 128, 0.16);
  border-color: rgba(74, 222, 128, 0.42);
}

/* La coche apparait en rebondissant : le changement de symbole se remarque
   davantage qu'une simple substitution. */
.pay-copy.done svg { animation: pay-copy-check 0.34s cubic-bezier(0.34, 1.56, 0.64, 1); }

@keyframes pay-copy-check {
  from { opacity: 0; transform: scale(0.4); }
  to   { opacity: 1; transform: scale(1); }
}

.pay-input { margin-top: 14px; }

/* -------- QR code -------- */

.pay-qr-card {
  padding: 16px;
  border-radius: 16px;
  background: linear-gradient(150deg, rgba(27, 15, 56, 0.7), rgba(10, 4, 19, 0.6));
  border: 1px solid var(--border);
  text-align: center;
  margin-bottom: 18px;
}

.pay-qr-card .pay-section-title { text-align: left; }

/* Fond blanc obligatoire : un QR sur fond sombre n'est pas lu de maniere
   fiable par les appareils photo. */
.pay-qr {
  width: 168px;
  height: 168px;
  margin: 0 auto 10px;
  padding: 9px;
  border-radius: 12px;
  background: #fff;
  box-shadow: 0 0 0 1px rgba(217, 70, 239, 0.35),
              0 10px 30px rgba(124, 58, 237, 0.3);
  animation: pay-qr-in 0.5s cubic-bezier(0.22, 1, 0.36, 1) backwards 0.1s;
}

.pay-qr svg { display: block; width: 100%; height: 100%; }

@keyframes pay-qr-in {
  from { opacity: 0; transform: scale(0.9) rotate(-2deg); }
  to   { opacity: 1; transform: none; }
}

.pay-qr-note { font-size: 11px; color: var(--muted); line-height: 1.5; }

/* -------- suivi du paiement -------- */

.pay-track {
  padding: 16px 18px;
  border-radius: 16px;
  background: linear-gradient(150deg, rgba(27, 15, 56, 0.7), rgba(10, 4, 19, 0.6));
  border: 1px solid var(--border);
}

.pay-track-list { list-style: none; margin: 0; padding: 0; }

.pay-track-step {
  position: relative;
  display: flex;
  gap: 13px;
  padding-bottom: 20px;
}

.pay-track-step:last-child { padding-bottom: 0; }

/* Trait vertical reliant les etapes : il s'arrete avant la derniere pastille
   pour ne pas depasser dans le vide. */
.pay-track-step:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 6px;
  top: 16px;
  bottom: 4px;
  width: 2px;
  border-radius: 1px;
  background: rgba(168, 85, 247, 0.2);
}

.pay-track-step.done:not(:last-child)::before { background: var(--v-400); }

.pay-track-dot {
  position: relative;
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  margin-top: 2px;
  border-radius: 50%;
  background: rgba(10, 4, 19, 0.9);
  border: 2px solid rgba(168, 85, 247, 0.35);
  transition: border-color 0.3s ease, background 0.3s ease;
}

.pay-track-step.done .pay-track-dot {
  background: var(--v-400);
  border-color: var(--v-300);
}

/* L'etape en cours pulse : c'est le seul point anime de la colonne, donc le
   regard s'y pose naturellement. */
.pay-track-step.current .pay-track-dot {
  border-color: var(--accent);
  background: var(--accent);
  animation: pay-pulse 1.9s ease-in-out infinite;
}

@keyframes pay-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(217, 70, 239, 0.55); }
  50%      { box-shadow: 0 0 0 7px rgba(217, 70, 239, 0); }
}

.pay-track-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.pay-track-text b { font-size: 12.5px; font-weight: 700; color: var(--muted); transition: color 0.3s ease; }
.pay-track-text i { font-style: normal; font-size: 11px; color: rgba(167, 139, 250, 0.65); }

.pay-track-step.done .pay-track-text b,
.pay-track-step.current .pay-track-text b { color: var(--v-100); }
.pay-track-step.current .pay-track-text i { color: var(--v-200); }

/* -------- pied -------- */

.pay-foot {
  padding: 16px 30px 26px;
  border-top: 1px solid rgba(168, 85, 247, 0.16);
  background: rgba(10, 4, 19, 0.35);
}

.pay-hint {
  font-size: 12px;
  color: var(--v-200);
  text-align: center;
  margin-bottom: 14px;
  min-height: 17px;
}

.pay-actions { display: flex; gap: 10px; }
.pay-actions .btn { flex: 1; }

/* -------- voile de reussite -------- */

/* display:flex l'emporte sur l'attribut hidden : sans ce garde, le voile de
   reussite recouvre l'ecran des l'ouverture et le paiement devient
   impossible. Meme precaution pour les autres blocs masques par defaut. */
.pay-success[hidden],
.pay-crypto-amount[hidden],
.pay-qr-card[hidden],
.pay-track[hidden],
.pay-wallet[hidden],
.pay-fields[hidden],
.pay-manual-toggle[hidden],
.pay-field[hidden] { display: none !important; }

/* -------- repli manuel -------- */

.pay-manual-toggle {
  display: flex;
  align-items: center;
  gap: 7px;
  margin: 0 auto 14px;
  padding: 7px 13px;
  border-radius: 999px;
  font-family: inherit;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--v-200);
  background: rgba(10, 4, 19, 0.4);
  border: 1px solid var(--border);
  cursor: pointer;
  transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}

.pay-manual-toggle:hover {
  color: var(--v-100);
  border-color: var(--v-400);
  background: rgba(124, 58, 237, 0.16);
}

.pay-manual-toggle svg {
  width: 10px;
  height: 7px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform 0.25s ease;
}

.pay-manual-toggle.open svg { transform: rotate(180deg); }

/* -------- paiement en un clic -------- */

/* Ce bloc precede la marche a suivre : quand il est disponible, c'est le
   chemin le plus court, les etapes manuelles deviennent un repli. */
.pay-wallet {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 17px 18px;
  margin-bottom: 20px;
  border-radius: 16px;
  background: linear-gradient(145deg, rgba(124, 58, 237, 0.26), rgba(27, 15, 56, 0.66));
  border: 1px solid rgba(217, 70, 239, 0.42);
  box-shadow: 0 12px 36px rgba(124, 58, 237, 0.24);
  animation: pay-wallet-in 0.45s cubic-bezier(0.22, 1, 0.36, 1) backwards;
}

@keyframes pay-wallet-in {
  from { opacity: 0; transform: translateY(-10px) scale(0.98); }
  to   { opacity: 1; transform: none; }
}

/* Halo qui respire derriere le bloc : profondeur sans clignotement. */
.pay-wallet::after {
  content: "";
  position: absolute;
  left: 50%;
  top: -70%;
  width: 240px;
  height: 240px;
  margin-left: -120px;
  border-radius: 50%;
  background: radial-gradient(50% 50% at 50% 50%, rgba(217, 70, 239, 0.3), transparent 70%);
  filter: blur(26px);
  animation: pay-wallet-breathe 6s ease-in-out infinite;
  pointer-events: none;
}

@keyframes pay-wallet-breathe {
  0%, 100% { opacity: 0.55; transform: scale(1); }
  50%      { opacity: 0.95; transform: scale(1.18); }
}

/* Lueur qui balaie lentement le bloc : attire l'oeil vers le raccourci sans
   clignoter ni reclamer l'attention. */
.pay-wallet::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 30%, rgba(217, 70, 239, 0.16) 50%, transparent 70%);
  transform: translateX(-100%);
  animation: pay-wallet-sweep 4.5s ease-in-out infinite;
  pointer-events: none;
}

@keyframes pay-wallet-sweep {
  0%, 55% { transform: translateX(-100%); }
  100%    { transform: translateX(100%); }
}

.pay-wallet-head { position: relative; z-index: 1; display: flex; align-items: center; gap: 11px; }

/* L'icone du reseau flotte legerement : un mouvement lent, presque
   imperceptible, qui signale que le bloc est vivant. */
.pay-wallet-icon {
  position: relative;
  flex: 0 0 auto;
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  border-radius: 11px;
  background: rgba(10, 4, 19, 0.6);
  border: 1px solid rgba(217, 70, 239, 0.34);
  box-shadow: 0 0 18px rgba(217, 70, 239, 0.22) inset;
  animation: pay-wallet-float 3.6s ease-in-out infinite;
}

@keyframes pay-wallet-float {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-3px); }
}

.pay-wallet-icon svg { width: 22px; height: 22px; display: block; }

.pay-wallet-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.pay-wallet-text b { font-size: 13.5px; font-weight: 700; color: #fff; }

.pay-wallet-text i {
  font-style: normal;
  font-family: var(--mono, "JetBrains Mono", monospace);
  font-size: 11.5px;
  color: var(--v-200);
}

/* Bouton principal : plus haut que les autres, avec un reflet qui le traverse
   au survol. C'est l'action que l'on veut voir choisie. */
.pay-wallet-btn {
  position: relative;
  z-index: 1;
  overflow: hidden;
  width: 100%;
  padding-top: 11px;
  padding-bottom: 11px;
  font-size: 13.5px;
  font-weight: 700;
  letter-spacing: 0.01em;
  transition: transform 0.18s ease, box-shadow 0.22s ease, filter 0.2s ease;
}

.pay-wallet-btn:not(:disabled):hover {
  transform: translateY(-1px);
  box-shadow: 0 10px 26px rgba(217, 70, 239, 0.42);
  filter: brightness(1.06);
}

.pay-wallet-btn:not(:disabled):active { transform: translateY(0); }

.pay-wallet-btn::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 35%, rgba(255, 255, 255, 0.28) 50%, transparent 65%);
  transform: translateX(-100%);
  transition: transform 0.6s ease;
}

.pay-wallet-btn:not(:disabled):hover::after { transform: translateX(100%); }

.pay-wallet-btn:disabled { opacity: 0.75; cursor: default; }

.pay-wallet-note {
  position: relative;
  z-index: 1;
  font-size: 11px;
  line-height: 1.5;
  color: var(--muted);
  text-align: center;
}

/* Virement transmis : le bloc bascule au vert et la lueur s'arrete, pour
   signaler qu'il n'y a plus rien a faire ici. */
.pay-wallet.sent {
  background: linear-gradient(145deg, rgba(22, 78, 52, 0.5), rgba(10, 25, 18, 0.6));
  border-color: rgba(74, 222, 128, 0.45);
  box-shadow: 0 10px 32px rgba(74, 222, 128, 0.16);
}

.pay-wallet.sent::before { animation: none; opacity: 0; }
.pay-wallet.sent .pay-wallet-note { color: rgba(209, 250, 229, 0.8); }

/* Portefeuille absent : le bloc reste a sa place mais s'efface visuellement.
   C'est une proposition, pas la marche a suivre — le QR juste en dessous
   permet deja de payer, et rien ne doit laisser croire le contraire. */
.pay-wallet.install {
  background: linear-gradient(145deg, rgba(124, 58, 237, 0.14), rgba(27, 15, 56, 0.5));
  border-color: rgba(167, 139, 250, 0.26);
  box-shadow: none;
}

/* Ni reflet ni halo ici : ces animations attirent l'oeil vers l'action
   principale, or celle-ci n'en est pas une. */
.pay-wallet.install::before,
.pay-wallet.install::after { animation: none; opacity: 0; }

.pay-wallet.install .pay-wallet-icon { animation: none; opacity: 0.75; }

.pay-success {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 30px;
  text-align: center;
  background: linear-gradient(160deg, rgba(15, 34, 24, 0.97), rgba(8, 5, 20, 0.98));
  animation: pay-success-in 0.4s ease;
}

@keyframes pay-success-in { from { opacity: 0; } to { opacity: 1; } }

.pay-success-burst {
  position: absolute;
  width: 320px;
  height: 320px;
  border-radius: 50%;
  background: radial-gradient(50% 50% at 50% 50%, rgba(74, 222, 128, 0.28), transparent 70%);
  animation: pay-burst 1.5s ease-out;
  pointer-events: none;
}

@keyframes pay-burst {
  from { transform: scale(0.3); opacity: 0.9; }
  to   { transform: scale(1.6); opacity: 0; }
}

.pay-success-badge { position: relative; width: 78px; height: 78px; margin-bottom: 10px; }
.pay-success-badge svg { width: 100%; height: 100%; }

/* Le cercle puis la coche se tracent : le trait est dessine en animant
   stroke-dashoffset, ce qui donne l'effet d'ecriture a la main. */
.pay-success-ring {
  fill: none;
  stroke: var(--ok);
  stroke-width: 2.5;
  stroke-dasharray: 145;
  stroke-dashoffset: 145;
  animation: pay-draw 0.55s ease forwards;
}

.pay-success-check {
  fill: none;
  stroke: var(--ok);
  stroke-width: 4;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 40;
  stroke-dashoffset: 40;
  animation: pay-draw 0.4s ease 0.45s forwards;
}

@keyframes pay-draw { to { stroke-dashoffset: 0; } }

.pay-success-title {
  font-size: 21px;
  font-weight: 800;
  color: #fff;
  animation: pay-step-in 0.4s ease 0.6s backwards;
}

.pay-success-sub {
  font-size: 13px;
  color: rgba(209, 250, 229, 0.75);
  animation: pay-step-in 0.4s ease 0.72s backwards;
}

/* -------- adaptation aux petits ecrans -------- */

@media (max-width: 860px) {
  /* Une seule colonne : le QR et le suivi passent sous la marche a suivre,
     dans cet ordre, car scanner vient avant attendre. */
  .pay-body { grid-template-columns: 1fr; gap: 20px; padding: 20px 20px 6px; }
  .pay-hero { padding: 22px 20px 20px; }
  .pay-hero h3 { font-size: 23px; }
  .pay-hero-amount { text-align: left; }
  .pay-crypto-amount, .pay-eur { justify-content: flex-start; }
  .pay-foot { padding: 14px 20px 22px; }
  .pay-actions { flex-direction: column; }
}

@media (max-width: 460px) {
  .pay-backdrop { padding: 12px 8px; }
  /* Sur petit ecran la valeur garde la pleine largeur, le bouton reste dans
     la colonne de droite : carre, il ne la vole plus comme le faisait le
     bouton texte, qui devait alors passer sous la valeur. */
  .pay-field { grid-template-columns: 1fr auto; grid-template-areas: "label bouton" "valeur bouton"; }
  .pay-crypto-figure { font-size: 21px; }
}

.orders-head { margin-top: 46px; }

.order-table .cell-name.mono {
  font-family: var(--mono, "JetBrains Mono", monospace);
  font-size: 12.5px;
  letter-spacing: 0.02em;
}

.plan.locked { opacity: 0.55; }
.plan.locked .plan-btn { cursor: not-allowed; }

/* ----------------------------------------------- verrouillage du panneau */

.lock-screen {
  text-align: center;
  padding: 56px 30px;
  border-radius: var(--radius);
  background: var(--panel);
  border: 1px dashed var(--v-500);
  backdrop-filter: blur(18px);
}

.lock-icon { font-size: 40px; margin-bottom: 14px; opacity: 0.85; }

.lock-screen h3 { font-size: 25px; font-weight: 900; margin-bottom: 10px; }

.lock-screen p {
  max-width: 460px;
  margin: 0 auto 24px;
  font-size: 14.5px;
  line-height: 1.7;
  color: var(--muted);
}

.plan-banner {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  padding: 15px 20px;
  margin-bottom: 20px;
  border-radius: 14px;
  background: rgba(124, 58, 237, 0.12);
  border: 1px solid var(--border);
}

.plan-banner p { flex: 1; min-width: 200px; font-size: 13px; color: var(--muted); }

.plan-chip {
  padding: 6px 14px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 900;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: #fff;
  background: linear-gradient(135deg, var(--v-400), var(--accent));
  white-space: nowrap;
}

/* Chaque palier a sa teinte : le role se lit d'un coup d'oeil. */
.plan-chip.decouverte,
.role-pill.decouverte { background: rgba(148, 163, 184, 0.26); color: #e2e8f0; border-color: rgba(148,163,184,0.4); }
.plan-chip.starter,
.role-pill.starter    { background: rgba(56, 189, 248, 0.24); color: #bae6fd; border-color: rgba(56,189,248,0.42); }
.plan-chip.pro,
.role-pill.pro        { background: linear-gradient(135deg, var(--v-400), var(--accent)); color: #fff; border-color: transparent; }
.plan-chip.business,
.role-pill.business   { background: rgba(251, 191, 36, 0.24); color: #fde68a; border-color: rgba(251,191,36,0.45); }
.plan-chip.illimite,
.role-pill.illimite   { background: linear-gradient(135deg, #22d3ee, #4ade80); color: #04231c; border-color: transparent; }

.role-tag.decouverte { background: rgba(148, 163, 184, 0.3); color: #e2e8f0; }
.role-tag.starter    { background: rgba(56, 189, 248, 0.3); color: #bae6fd; }
.role-tag.pro        { background: linear-gradient(135deg, var(--v-400), var(--accent)); color: #fff; border-color: transparent; }
.role-tag.business   { background: rgba(251, 191, 36, 0.3); color: #fde68a; }
.role-tag.illimite   { background: linear-gradient(135deg, #22d3ee, #4ade80); color: #04231c; border-color: transparent; }

.plan.owned { border-color: var(--ok); box-shadow: 0 22px 60px rgba(74, 222, 128, 0.22); }

.plan-flag.owned-flag {
  background: linear-gradient(135deg, var(--ok), #16a34a);
  box-shadow: 0 5px 18px rgba(74, 222, 128, 0.5);
}

/* --------------------------------------------- owner / mouvement reduit  */

/* Place en fin de fichier a dessein : les regles owner arrivent apres le bloc
   prefers-reduced-motion general, et a specificite egale c'est la derniere
   declaration qui l'emporte. Ce correctif doit donc venir apres elles. */
@media (prefers-reduced-motion: reduce) {
  /* Le correctif du pseudo owner peint en background-clip: text a disparu avec
     le pseudo lui-meme : la barre n'affiche plus que la photo, il n'y a plus
     de texte a rendre lisible ici. */

  /* La lumiere qui traverse l'etiquette est un decor : figee en cours de
     course, elle laisserait une bande claire posee de travers. On la retire
     entierement, le contour violet suffit a tenir la forme. */
  .user-chip .role-tag::before { display: none; }

  .user-chip .role-tag.owner { animation: none; }

  /* Contour anime de la pastille : le degrade s'arreterait sur une position
     arbitraire, laissant un liseré clair d'un seul cote. On garde le contour
     mais immobile, centre sur sa partie lumineuse. */
  .user-chip:hover::before {
    animation: none;
    background-position: 50% 0;
  }

  /* La fleche de deconnexion ne glisse plus ; la teinte au survol suffit a
     signaler le bouton. */
  .chip-logout-icon { transition: none; }
  .chip-logout:hover:not(:disabled) .chip-logout-icon { transform: none; }

  /* Bloc de paiement : les decors animes sont retires plutot que figes. Un
     balayage arrete en milieu de course laisserait une bande claire posee sur
     le bouton, et un halo fige masquerait le texte. */
  .pay-wallet::before,
  .pay-wallet::after,
  .pay-wallet-btn::after { display: none; }

  .pay-wallet-icon { animation: none; }
}

/* ------------------------------------------------------------- support ---
   Le formulaire et la liste des demandes reposent sur .panel, .field et
   .state-tag deja definis plus haut : seules la mise en colonnes, les cartes
   de demande et le fil de discussion demandent des regles propres. */

/* Le mode d'emploi ferme la page : la marge passe donc au-dessus, pour le
   detacher de la grille qui le precede. */
.support-steps { margin: 34px 0 0; }

/* Ces etapes se lisent, elles ne se parcourent pas : elles s'allument au
   survol sans se deplacer. Le glissement de .step reste en vigueur sur la page
   Fonctions, ou il accompagne une lecture sequentielle. */
.support-steps .step {
  /* transform retire de la transition : rien ne bouge plus ici. */
  transition: border-color 0.2s, background-color 0.2s, box-shadow 0.2s;
}

.support-steps .step:hover {
  transform: none;
  border-color: var(--v-300);
  background: rgba(124, 58, 237, 0.1);
  box-shadow: 0 0 0 1px rgba(168, 85, 247, 0.12), 0 6px 22px rgba(124, 58, 237, 0.16);
}

/* La pastille s'eclaire avec la carte : c'est elle que l'oeil suit. */
.support-steps .step:hover .step-num {
  box-shadow: 0 4px 22px rgba(124, 58, 237, 0.75);
}

/* Deux colonnes de largeur egale : le formulaire et le suivi ont la meme
   importance, l'un ne doit pas ecraser l'autre. */
.support-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
  align-items: start;
}

@media (max-width: 900px) {
  /* En dessous, deux colonnes rendraient le textarea trop etroit pour ecrire
     confortablement : on empile, formulaire en premier. */
  .support-grid { grid-template-columns: 1fr; }
}

.support-grid .panel-head { margin-bottom: 20px; }

/* --- liste des demandes cote client --- */

.ticket-list {
  display: flex;
  flex-direction: column;
  gap: 11px;
  /* Bornee puis defilante : au dela d'une poignee de fils, la page entiere
     s'allongerait sans fin sous le formulaire. */
  max-height: 470px;
  overflow-y: auto;
  /* Le decalage de 3 px au survol depasse la largeur du conteneur et faisait
     apparaitre une barre de defilement horizontale sous la liste. Rien n'a a
     defiler lateralement ici : on la supprime. */
  overflow-x: hidden;
  /* Marge a droite pour la barre verticale, sinon elle chevauche le bouton de
     retrait des qu'il y a assez de demandes pour faire defiler. */
  padding-right: 4px;
}

.ticket-empty {
  color: var(--muted);
  font-size: 13px;
  line-height: 1.6;
  padding: 22px 4px;
  text-align: center;
}

.ticket-card {
  display: block;
  width: 100%;
  text-align: left;
  padding: 14px 16px;
  border-radius: 14px;
  border: 1px solid var(--border);
  background: rgba(10, 4, 19, 0.45);
  color: var(--text);
  font-family: inherit;
  cursor: pointer;
  transition: border-color 0.18s, background 0.18s, transform 0.18s;
}

/* La carte s'allume sans bouger : elle porte desormais un bouton de retrait,
   et viser une cible qui se decale au moment ou la souris l'approche est
   desagreable. */
.ticket-card:hover {
  border-color: var(--v-300);
  background: rgba(124, 58, 237, 0.12);
}

.ticket-card:focus-visible {
  outline: none;
  border-color: var(--v-300);
  box-shadow: 0 0 0 3px rgba(168, 85, 247, 0.16);
}

.ticket-card-head {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 8px;
}

.ticket-card-ref { font-size: 12px; color: var(--v-200); }

/* Pastille « reponse » : signale un fil qui a bouge sans avoir a l'ouvrir. */
.ticket-card-new {
  padding: 3px 9px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  background: linear-gradient(135deg, var(--v-400), var(--accent));
  color: #fff;
}

.ticket-card-subject {
  font-size: 14px;
  font-weight: 600;
  line-height: 1.45;
  margin-bottom: 6px;
  word-break: break-word;
}

.ticket-card-meta { font-size: 11.5px; color: var(--muted); }

/* Derniere ligne : informations a gauche, bouton de retrait a droite. Le
   bouton vit dans le flux plutot qu'en position absolue, sinon il recouvre la
   ligne des qu'elle est longue. */
.ticket-card-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.ticket-card-foot .ticket-card-meta { min-width: 0; }

/* Bouton de retrait. Rouge des le repos, comme les autres actions
   destructrices du panneau : la couleur annonce ce que fait le bouton avant
   qu'on le lise. */
.ticket-card-del {
  flex-shrink: 0;
  padding: 5px 11px;
  border-radius: 9px;
  border: 1px solid rgba(251, 113, 133, 0.35);
  background: rgba(251, 113, 133, 0.16);
  color: var(--err);
  font-family: inherit;
  font-size: 11.5px;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.18s, background-color 0.18s, border-color 0.18s, box-shadow 0.18s;
}

/* Revele au survol de la carte. Le focus clavier le montre aussi, sans quoi il
   serait atteignable par tabulation tout en restant invisible. */
.ticket-card:hover .ticket-card-del,
.ticket-card-del:focus-visible { opacity: 1; }

.ticket-card-del:hover {
  background: rgba(251, 113, 133, 0.28);
  border-color: rgba(251, 113, 133, 0.5);
  box-shadow: 0 6px 18px rgba(251, 113, 133, 0.28);
}

.ticket-card-del:focus-visible {
  outline: none;
  border-color: rgba(251, 113, 133, 0.6);
  box-shadow: 0 0 0 3px rgba(251, 113, 133, 0.2);
}

/* Sur ecran tactile il n'y a pas de survol : le bouton reste visible, sinon il
   serait inatteignable. */
@media (hover: none) {
  .ticket-card-del { opacity: 1; }
}

/* Marque cote admin : le client a retire ce fil de sa liste. Gris et non
   colore — c'est une information de contexte, pas une alerte. */
.hidden-tag {
  display: inline-block;
  margin-left: 6px;
  padding: 3px 8px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: rgba(10, 4, 19, 0.5);
  color: var(--muted);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* --- support prioritaire (formules Pro, Business, Illimite) --- */

/* Or plutot que violet : la couleur du site sert deja a tout, elle ne
   distinguerait rien ici. L'or dit « formule haute » d'un coup d'oeil. */
.prio-banner {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 13px 15px;
  margin-bottom: 20px;
  border-radius: 14px;
  border: 1px solid rgba(251, 191, 36, 0.38);
  background: linear-gradient(135deg, rgba(251, 191, 36, 0.16), rgba(217, 119, 6, 0.08));
}

.prio-banner[hidden] { display: none; }

.prio-star {
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  color: #fbbf24;
  filter: drop-shadow(0 0 8px rgba(251, 191, 36, 0.5));
}

.prio-star svg { width: 100%; height: 100%; display: block; }

.prio-text { display: flex; flex-direction: column; gap: 3px; }
.prio-text b { font-size: 13px; color: #fde68a; }
.prio-text i { font-size: 11.5px; color: var(--muted); font-style: normal; line-height: 1.5; }

/* Etiquette compacte, posee a cote de l'etat dans les listes et le fil. */
.prio-tag {
  padding: 3px 9px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  white-space: nowrap;
  color: #78350f;
  background: linear-gradient(135deg, #fbbf24, #f59e0b);
}

.prio-tag[hidden] { display: none; }

/* Les lignes du tableau admin s'ouvrent au clic : le curseur doit l'annoncer. */
.ticket-row { cursor: pointer; }

/* Liseré doré en bord de ligne : lisible en balayant la colonne de gauche,
   sans coûter une colonne supplémentaire au tableau. */
.ticket-row.is-prio td:first-child { box-shadow: inset 3px 0 0 #fbbf24; }
.ticket-row.is-prio { background: rgba(251, 191, 36, 0.05); }
.ticket-row.is-prio:hover { background: rgba(251, 191, 36, 0.1); }

/* --- page conversation --- */

/* Deux colonnes : les demandes a gauche, le fil a droite. La hauteur est
   bornee pour que la zone de saisie reste visible sans faire defiler la page,
   comme dans une messagerie. */
.chat {
  display: grid;
  grid-template-columns: 300px 1fr;
  gap: 18px;
  height: min(78vh, 720px);
}

.chat-side,
.chat-main {
  display: flex;
  flex-direction: column;
  min-height: 0;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--panel);
  backdrop-filter: blur(18px);
  overflow: hidden;
}

.chat-side-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 16px 16px 14px;
  border-bottom: 1px solid var(--border);
}

.chat-side-head h3 { font-size: 15px; }

/* Seule cette colonne defile : l'en-tete reste en vue quand la liste s'allonge. */
.chat-side-list {
  flex: 1;
  overflow-y: auto;
  padding: 10px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Une entree de la liste : avatar, puis pseudo, sujet et extrait empiles. */
.chat-item {
  display: flex;
  gap: 11px;
  width: 100%;
  padding: 11px 12px;
  border-radius: 13px;
  border: 1px solid transparent;
  background: transparent;
  text-align: left;
  cursor: pointer;
  transition: background-color 0.18s, border-color 0.18s;
}

.chat-item:hover { background: rgba(124, 58, 237, 0.12); }

/* La demande ouverte reste reperable pendant qu'on lit le fil a cote. */
.chat-item.active {
  background: rgba(124, 58, 237, 0.2);
  border-color: rgba(168, 85, 247, 0.4);
}

/* Liseré doré, comme dans le tableau admin : la formule haute se repere en
   balayant la colonne, sans lire le pseudo. */
.chat-item.is-prio { box-shadow: inset 3px 0 0 #fbbf24; }

.chat-item-text { flex: 1; min-width: 0; }

.chat-item-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}

.chat-item-name {
  font-size: 13px;
  font-weight: 700;
  color: var(--v-100);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat-item-date { font-size: 10px; color: var(--muted); flex-shrink: 0; }

.chat-item-subject {
  font-size: 12px;
  color: var(--v-200);
  margin: 2px 0 3px;
}

/* Extrait du dernier message et sujet sur une ligne chacun : au-dela, la
   colonne s'allonge et on perd la vue d'ensemble de la file. */
.chat-item-subject,
.chat-item-last {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat-item-last { font-size: 11.5px; color: var(--muted); }

/* Un fil ou le dernier mot revient a l'autre attend une reponse : la pastille
   le signale sans ouvrir. */
.chat-item-dot {
  flex-shrink: 0;
  align-self: center;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 10px rgba(217, 70, 239, 0.8);
}

.chat-empty {
  padding: 18px 12px;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--muted);
  text-align: center;
}

/* Pastille a l'initiale : un vrai portrait n'existe pas ici, et une lettre
   identifie mieux qu'une silhouette generique repetee a chaque ligne. */
.chat-avatar {
  flex-shrink: 0;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  font-size: 13px;
  font-weight: 800;
  color: #fff;
  background: linear-gradient(135deg, var(--v-400), var(--accent));
  box-shadow: 0 3px 12px rgba(124, 58, 237, 0.42);
}

.chat-avatar.lg { width: 42px; height: 42px; font-size: 16px; }

/* L'equipe porte une teinte distincte du client : dans un fil, la couleur de
   l'avatar dit qui parle avant que le nom soit lu. */
.chat-avatar.staff { background: linear-gradient(135deg, #6d28d9, #4c1d95); }

.chat-head {
  display: flex;
  align-items: center;
  gap: 13px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--border);
}

.chat-head-text { flex: 1; min-width: 0; }
.chat-head-text h3 { font-size: 16px; word-break: break-word; }

.chat-head-meta {
  font-size: 12px;
  color: var(--muted);
  margin-top: 2px;
  word-break: break-word;
}

.chat-head-tags {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

/* Reference, categorie et date d'ouverture : utiles mais secondaires, donc
   sur une ligne discrete sous l'en-tete plutot que dans le titre. */
.chat-sub {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  padding: 9px 18px;
  font-size: 11px;
  color: var(--muted);
  border-bottom: 1px solid var(--border);
  background: rgba(10, 4, 19, 0.35);
}

.chat-sub .mono { color: var(--v-200); }

.chat-body {
  flex: 1;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 18px;
}

/* Separateur de jour : dans un echange etale sur une semaine, l'heure seule
   ne dit pas de quel jour il s'agit. */
.chat-day {
  align-self: center;
  margin: 12px 0 10px;
  padding: 4px 12px;
  border-radius: 999px;
  font-size: 10.5px;
  color: var(--muted);
  background: rgba(10, 4, 19, 0.6);
  border: 1px solid var(--border);
}

.chat-day:first-child { margin-top: 0; }

/* Une ligne = un message, avec son avatar. L'alignement porte la distinction
   entre les deux voix ; l'auteur ecrit n'est la qu'en renfort. */
.chat-row {
  display: flex;
  align-items: flex-end;
  gap: 9px;
  max-width: 74%;
  margin-top: 9px;
}

/* Messages suivants d'un meme auteur : colles au precedent, ils forment un
   bloc visuel au lieu d'une suite de bulles detachees. */
.chat-row.grouped { margin-top: 2px; }

.chat-row.mine {
  align-self: flex-end;
  flex-direction: row-reverse;
}

/* L'emplacement de l'avatar est conserve dans un groupe, sinon les bulles
   suivantes se decaleraient vers le bord. */
.chat-row.grouped .chat-avatar { visibility: hidden; }

.chat-bubble {
  padding: 10px 14px;
  border-radius: 16px;
  border: 1px solid var(--border);
  background: rgba(10, 4, 19, 0.55);
  min-width: 0;
}

/* Coin rabattu du cote de l'auteur : la bulle pointe vers celui qui parle. */
.chat-row:not(.mine) .chat-bubble { border-bottom-left-radius: 5px; }

.chat-row.mine .chat-bubble {
  border-bottom-left-radius: 16px;
  border-bottom-right-radius: 5px;
  background: linear-gradient(135deg, rgba(124, 58, 237, 0.42), rgba(168, 85, 247, 0.26));
  border-color: rgba(168, 85, 247, 0.42);
}

/* Dans un groupe, seule la premiere bulle garde le coin rabattu. */
.chat-row.grouped:not(.mine) .chat-bubble { border-bottom-left-radius: 16px; }
.chat-row.grouped.mine .chat-bubble { border-bottom-right-radius: 16px; }

.chat-author {
  display: block;
  font-size: 11px;
  font-weight: 700;
  color: var(--v-200);
  margin-bottom: 4px;
}

/* pre-wrap : les retours a la ligne saisis sont conserves, alors que le texte
   reste insere via textContent, donc jamais interprete. */
.chat-text {
  font-size: 13.5px;
  line-height: 1.6;
  white-space: pre-wrap;
  word-break: break-word;
}

.chat-time {
  display: block;
  margin-top: 5px;
  font-size: 10px;
  color: var(--muted);
  text-align: right;
}

.chat-compose {
  display: flex;
  align-items: flex-end;
  gap: 10px;
  padding: 13px 16px;
  border-top: 1px solid var(--border);
  background: rgba(10, 4, 19, 0.4);
}

/* display:flex l'emporterait sur l'attribut hidden : sans ce garde, la zone de
   saisie resterait visible sur une demande close. */
.chat-compose[hidden] { display: none; }

/* La zone grandit avec le texte (hauteur pilotee en JS) : ecrire dix lignes
   dans une boite de trois oblige a defiler pour se relire. */
.chat-compose textarea {
  flex: 1;
  resize: none;
  max-height: 140px;
  border-radius: 14px;
}

/* Rond plutot que rectangle : la fleche seule, sans libelle, se lit mieux
   dans une pastille. */
.chat-send {
  flex-shrink: 0;
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border: none;
  border-radius: 50%;
  color: #fff;
  cursor: pointer;
  background: linear-gradient(135deg, var(--v-400), var(--accent));
  box-shadow: 0 4px 16px rgba(124, 58, 237, 0.5);
  transition: transform 0.16s, box-shadow 0.16s, opacity 0.16s;
}

.chat-send svg { width: 19px; height: 19px; }

.chat-send:hover {
  transform: scale(1.07);
  box-shadow: 0 6px 22px rgba(124, 58, 237, 0.7);
}

.chat-send:active { transform: scale(0.95); }

.chat-send:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}

/* Fil clos : la saisie disparait plutot que d'etre grisee, et un mot explique
   pourquoi — un champ inerte sans explication passe pour une panne. */
.chat-closed {
  padding: 15px 18px;
  border-top: 1px solid var(--border);
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--muted);
  text-align: center;
  background: rgba(10, 4, 19, 0.4);
}

@media (max-width: 900px) {
  /* Une colonne de 300 px etoufferait le fil : la liste passe en bandeau
     horizontal au-dessus, ou l'on fait defiler du doigt. */
  .chat {
    grid-template-columns: 1fr;
    height: auto;
  }

  .chat-side { max-height: 210px; }

  .chat-side-list {
    flex-direction: row;
    overflow-x: auto;
    overflow-y: hidden;
  }

  /* Chaque entree garde une largeur lisible au lieu d'etre comprimee par le
     nombre de demandes. */
  .chat-item { width: 240px; flex-shrink: 0; }

  /* Sans hauteur fixe, le fil s'etire indefiniment et la zone de saisie
     descend hors de l'ecran. */
  .chat-main { height: 68vh; }

  .chat-row { max-width: 88%; }
}
